Lazarus

Free Pascal => Beginners => Topic started by: Spoonhorse on June 11, 2021, 01:37:10 pm

Title: Using the code from Inno Setup to use Scintilla
Post by: Spoonhorse on June 11, 2021, 01:37:10 pm
I'm putting this in Beginners because I suspect it's a newbie blunder; if it's something more difficult I'll delete it and put it somewhere else.

I got typo's Delphi-to-FPC conversion of ScintEdit.pas from here (https://forum.lazarus.freepascal.org/index.php/topic,26331.15.html). I got ScintInt.pas and isscint.dll from the Inno Setup source. I put them in my project folder and added ScintEdit to my uses clause. So far so good, that compiles.

But the next step I thought was to put

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var ScEdit: TScintEdit;
  3. begin
  4. ScEdit:=TScintEdit.Create(Form1);
  5. end;

and see what happens. But if I do that then the program stops a couple of seconds after starting, without me pressing the button and without it displaying any error messages.

My guess is that this is because it's only at runtime that it finds that there's something wrong with the .dll / it can't find the .dll, and this is somewhat confirmed by the fact that if in ScintInt.pas I replace

Code: Pascal  [Select][+][-]
  1. function Scintilla_DirectFunction(ptr: Pointer; iMessage: Cardinal;
  2.   wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; external 'isscint.dll';

with

Code: Pascal  [Select][+][-]
  1. function Scintilla_DirectFunction(ptr: Pointer; iMessage: Cardinal;
  2.   wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; external 'edward.bear';

it has exactly the same effect.

Tipped off by this thread (https://forum.lazarus.freepascal.org/index.php?topic=14092.0) I tried adding {$MODE DELPHI} to ScintInt.pas but with no visible effect.

And that's all I've got, I know little to nothing about .dlls or Delphi-to-Lazarus conversion, can anyone help? Thanks.

I'm running the latest FPC/Lazarus on Windows 10. (And yes, I do have to use Scintilla and not SynEdit, sorry.)
Title: Re: Using the code from Inno Setup to use Scintilla
Post by: Spoonhorse on June 15, 2021, 07:42:41 am
I rolled my own syntax highlighter using RichMemo which is a sort of solution to the problem. (I already did stuff to handle indentation in the event handler of a TMemo and will merge them together). A code review would be welcome but it works. (ETA ... well it does now.)

You can see you could use it for things other than Python if you wanted to, you could make it more flexible by (a) having the rules as a property of the component (b) allowing the rules to specify the group of the regex to be highlighted.

Code: Pascal  [Select][+][-]
  1. unit PythonMemo;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, RichMemo, RegExpr, Graphics, LazUtf16;
  9.  
  10. type
  11.  
  12. TRule =  record
  13.          RegExp: string;
  14.          Color: TColor;
  15.          Style: TFontstyles;
  16.          end;
  17.  
  18. { TRichMemo }
  19.  
  20. TRichMemo = class(RichMemo.TRichMemo)
  21.   protected
  22.     procedure Change; override;
  23.   private
  24.     var Count: integer ;
  25.         FRules: array[1..80] of TRule;
  26.  
  27.   const
  28.  
  29.     LKEYWORDS :  TStringArray =
  30.                 ('and', 'assert', 'break', 'class', 'continue', 'def',
  31.                 'del', 'elif', 'else', 'except', 'exec', 'finally',
  32.                 'for', 'from', 'global', 'if', 'import', 'in',
  33.                 'is', 'lambda', 'not', 'or', 'pass', 'print',
  34.                 'raise', 'return', 'try', 'while', 'yield',
  35.                 'none', 'true', 'false');
  36.  
  37.     LSYMBOLS :    TStringArray =
  38.                  ('=',
  39.                  '==', '!=', '<', '<=', '>', '>=',
  40.                  '\+', '-', '\*', '/', '\%',
  41.                  '\+=', '-=', '\*=', '/=', '\%=',
  42.                  '\^', '\|', '\&', '\~', '>>', '<<',
  43.                  '\{', '\}', '\[', '\]', '\(', '\)',
  44.                  '\.', ',', ';', ':', '\\', '!', '@', '&');
  45.   public
  46.     constructor Create(AOwner: TComponent); override;
  47.  
  48. end;
  49.  
  50. implementation
  51.  
  52. constructor TRichMemo.Create(AOwner: TComponent);
  53.  
  54.     procedure add(s: string; c: TColor; fs: TFontStyles);
  55.       begin
  56.       Count:=Count+1;
  57.       FRules[Count].RegExp:=s;
  58.       FRules[Count].Color:=c;
  59.       FRules[Count].Style:=fs;
  60.       end;
  61.  
  62. var i: integer;
  63.  
  64. begin
  65. inherited;
  66. Count:=0;
  67. for i:=0 to length(LKEYWORDS)-1 do
  68.   add('\b'+LKEYWORDS[i]+'\b', clBlack, [fsBold]);
  69. for i:=0 to length(LSYMBOLS)-1 do
  70.   add(LSYMBOLS[i], clRed, []);
  71. add('"[^"\\]*(\\.[^"\\]*)*"', clBlue, []);
  72. add('''[^''\\]*(\\.[^''\\]*)*''', clBlue, []);
  73. add('\b[+-]?[0-9]+[lL]?\b', clBlue, []);
  74. add('\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', clBlue, []);
  75. add('\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', clBlue, []);
  76. add('#[^\n]*', clGreen, [fsItalic]);
  77. Change;
  78. end;
  79.  
  80. procedure TRichMemo.Change; //Up(var Key: Word; Shift: TShiftstate);
  81. var n: integer;
  82.     i: integer;
  83.     RE: TregExpr;
  84.     off: integer;
  85.     TF: TFont;
  86. begin
  87. inherited;
  88. TF:=TFont.Create();
  89. off:=0;       // Keeps track of the position of the start of the Line of the RichMemo in terms of the underlying text string.
  90. for n:=0 to Lines.Count-1 do
  91. begin
  92.   if Lines[n]<>'' then // Because TRegExpr can't cope with empty strings.
  93.       begin
  94.       TF.Name:=Font.Name; TF.Size:=Font.Size; TF.Color:=clBlack; TF.Style:=[];
  95.       SetTextAttributes(off,length(Lines[n])+1,TF); // Make everything plain black including the newlines (hence the +1).
  96.       RE:=TRegExpr.Create;
  97.       for i:=1 to Count do
  98.         begin
  99.         RE.Expression:=FRules[i].RegExp;
  100.         RE.InputString:=self.Lines[n];
  101.         if RE.Exec then
  102.            repeat
  103.            TF.Color:=FRules[i].Color;
  104.            TF.Style:=FRules[i].Style;
  105.            self.SetTextAttributes(off+RE.MatchPos[0]-1,RE.MatchLen[0],TF)
  106.            until not RE.ExecNext;
  107.         end;
  108.       RE.Free;
  109.       end;
  110.   off:=off+utf16length(Lines[n])+1; // +1 for the newline.
  111.   end;
  112. TF.Free;
  113. end;
  114.  
  115. end.

---

(As implemented this is treacle-slow because it processes *every* line. To speed it up I need to process only the line you're typing on, except then I need special handling for pasting. I'll post another version later, also with the indentation, that bit went easy.)

---

P.S: As I'm still fiddling with it this is implemented as an interposer class. Not everyone knows about these but they're great if you don't get silly with them. You use them as follows:

If you want to modify an existing visual component (you could do it with any old class but I don't see the point) then instead of the usual definition where you put something like TNewComponent = class(TComponent) you put TComponent = class(UnitOfComponent.TComponent).

You must put UnitOfComponent in the uses clause of the unit where you do this.

Then you can mess with the class in the code in the usual way.

Then to use it, in any unit you declare in the uses clause both UnitOfComponent and whatever unit you put the new class in, in that order.

Hey presto, if you now use the IDE to slap the component onto the form of the unit where you did those declarations, it will have the behavior of your new component.

This is a terrible thing to do for long-term maintainable code but it's great for development.
Title: Re: Using the code from Inno Setup to use Scintilla
Post by: Spoonhorse on June 16, 2021, 08:32:00 pm
So I guess this is pretty much done if anyone's interested. This is an extremely lightweight editing component for Python (it does syntax highlighting and Pythonesque indentation) easily adaptable for the language of your choice. (Maybe I should make that such adaptability a property of the class but not today.)

Code: Pascal  [Select][+][-]
  1. unit PythonMemo;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, RichMemo, RegExpr, Graphics, LazUTF16;
  9.  
  10. type
  11.  
  12. TRule =  record
  13.          RegExp: string;
  14.          Color: TColor;
  15.          Style: TFontstyles;
  16.          end;
  17.  
  18. { TRichMemo }
  19.  
  20. TRichMemo = class (RichMemo.TRichMemo)
  21.   protected
  22.     procedure KeyDown(var Key: word; Shift: TShiftState); override;
  23.     procedure KeyUp(var Key: word; Shift: TShiftState); override;
  24.     procedure Click; override;
  25.     procedure DoEnter; override;
  26.   private
  27.     var Count: integer;
  28.         FRules: array[1..80] of TRule;
  29.         OldY: integer;
  30.         Toggle: Boolean;
  31.         OldBlank: boolean;
  32.   public
  33.     function RichXYtoX(X,Y:integer):integer;
  34.     procedure TouchLine(n: integer);
  35.   const
  36.  
  37.     LKEYWORDS :  TStringArray =
  38.                 ('and', 'assert', 'break', 'class', 'continue', 'def',
  39.                 'del', 'elif', 'else', 'except', 'exec', 'finally',
  40.                 'for', 'from', 'global', 'if', 'import', 'in',
  41.                 'is', 'lambda', 'not', 'or', 'pass', 'print',
  42.                 'raise', 'return', 'try', 'while', 'yield',
  43.                 'None', 'True', 'False');
  44.  
  45.     LSYMBOLS :    TStringArray =
  46.                  ('=',
  47.                  '==', '!=', '<', '<=', '>', '>=',
  48.                  '\+', '-', '\*', '/', '\%',
  49.                  '\+=', '-=', '\*=', '/=', '\%=',
  50.                  '\^', '\|', '\&', '\~', '>>', '<<',
  51.                  '\{', '\}', '\[', '\]', '\(', '\)',
  52.                  '\.', ',', ';', ':', '\\', '!', '@', '&');
  53.   public
  54.     constructor Create(AOwner: TComponent); override;
  55. end;
  56.  
  57. implementation
  58.  
  59. // Calling this RichXYtoX to remind myself that it is NOT compatible with
  60. // the regular TMemo, which should have ... (Lines[i])+2;
  61. function TRichMemo.RichXYtoX(X,Y:integer): integer;
  62. var i:integer;
  63.     L:integer;
  64. begin
  65. L:=utf16length(Text);
  66. RichXYtoX:=0;
  67. for i:=0 to Y-1 do
  68.     begin
  69.     RichXYtoX:=RichXYtoX+utf16length(Lines[i]);
  70.     if (RichXYtoX < L) and (Text[RichXYtoX+1]=#13) then
  71.        begin
  72.        RichXYtoX:=RichXYtoX + 1;
  73.        end;
  74.     end;
  75. RichXYtoX:=RichXYtoX+X;
  76. end;
  77.  
  78. function firstnonspace(s:string):integer;
  79. begin
  80. // To be more accurate, it returns the number of leading spaces plus one. So the empty string returns 1.
  81. firstnonspace:=1; while (s<>'') and (s[firstnonspace] = ' ') and (firstnonspace<=length(s)) do firstnonspace:=firstnonspace+1;
  82. end;
  83.  
  84.  
  85. constructor TRichMemo.Create(AOwner: TComponent);
  86.  
  87.     procedure add(s: string; c: TColor; fs: TFontStyles);
  88.       begin
  89.       Count:=Count+1;
  90.       FRules[Count].RegExp:=s;
  91.       FRules[Count].Color:=c;
  92.       FRules[Count].Style:=fs;
  93.       end;
  94.  
  95. var i: integer;
  96.  
  97. begin
  98. inherited;
  99. OldY:=0;
  100. Count:=0;
  101. OldBlank:=false;
  102. Toggle:=false;
  103. for i:=0 to length(LKEYWORDS)-1 do
  104.   add('\b'+LKEYWORDS[i]+'\b', clBlack, [fsBold]);
  105. for i:=0 to length(LSYMBOLS)-1 do
  106.   add(LSYMBOLS[i], clRed, []);
  107. add('"[^"\\]*(\\.[^"\\]*)*"', clBlue, []);
  108. add('''[^''\\]*(\\.[^''\\]*)*''', clBlue, []);
  109. add('\b[+-]?[0-9]+[lL]?\b', clBlue, []);
  110. add('\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', clBlue, []);
  111. add('\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', clBlue, []);
  112. add('#[^\n]*', clGreen, [fsItalic]);
  113. end;
  114.  
  115.  
  116. procedure TRichMemo.TouchLine(n: integer);
  117. var RE: TregExpr;
  118.     TF: TFont;
  119.     off: integer;
  120.     i: integer;
  121. begin
  122. if Lines[n]='' then Exit;
  123. off:=RichXYtoX(0,n);
  124. TF:=TFont.Create();
  125. TF.Name:=Font.Name; TF.Size:=Font.Size; TF.Color:=clBlack; TF.Style:=[];
  126. SetTextAttributes(off,utf16length(Lines[n])+1,TF); // Make everything plain black including the newlines (hence the +1).
  127. RE:=TRegExpr.Create;
  128. for i:=1 to Count do
  129.   begin
  130.   RE.Expression:=FRules[i].RegExp;
  131.   RE.InputString:=self.Lines[n];
  132.   if RE.Exec then
  133.      repeat
  134.      TF.Color:=FRules[i].Color;
  135.      TF.Style:=FRules[i].Style;
  136.      self.SetTextAttributes(off+RE.MatchPos[0]-1,RE.MatchLen[0],TF)
  137.      until not RE.ExecNext;
  138.   end;
  139. RE.Free;
  140. TF.Free;
  141. end;
  142.  
  143.  
  144. procedure TRichMemo.KeyUp(var Key: Word; Shift: TShiftstate);
  145. var i,j: integer;
  146.     x,y: integer;
  147.     tn: integer;
  148. begin
  149.  
  150. x:=self.CaretPos.X;
  151. y:=self.CaretPos.Y;
  152.  
  153. //We handle the enter, tab, and backspace keys.
  154.  
  155. if (Key=13) and (y>0) and (Lines[y-1]<>'') then          // enter
  156.     begin
  157.        if not Toggle then
  158.        begin
  159.        j:=firstnonspace(Lines[y-1]);
  160.        if Lines[y-1][length(Lines[y-1])] = ':' then j:=j+4;
  161.        Lines[y]:=StringOfChar(' ',j-1)+Lines[y];
  162.        CaretPos := TPoint.Create(j-1,y);
  163.        end;
  164.     end;
  165.  
  166. if Key=9 then           // tab
  167.     begin
  168.        if not Toggle then
  169.        begin
  170.        Lines[y]:=copy(Lines[y],1,x)+'    '+copy(Lines[y],x+1,length(Lines[y])-x);
  171.        CaretPos := TPoint.Create(x+4,y);
  172.        key:=0;
  173.        end;
  174.     end;
  175.  
  176. if (Key=8) and (x>0) and (copy(Lines[y],1,x) = stringofchar(' ',x)) and OldBlank then           // backspace
  177.     begin
  178.        if not Toggle then
  179.        begin
  180.        tn := 4*((x-1) div 4);    // Because this is how deleting whitespace should work, change the code or sue me.
  181.        Lines[y]:=stringofchar(' ',tn)+copy(Lines[y],x+1,length(Lines[y])-x);
  182.        CaretPos := TPoint.Create(tn,y);
  183.        key:=0;
  184.        end;
  185.     end;
  186.  
  187. // Then we do the syntax highlighting.
  188.  
  189. x:=self.CaretPos.X;
  190. y:=self.CaretPos.Y;
  191. if y<=OldY then
  192.    TouchLine(y)
  193. else
  194.    for i:=OldY to y do
  195.        TouchLine(i);
  196.  
  197. inherited;
  198. end;
  199.  
  200.  
  201. procedure TRichMemo.KeyDown(var Key: Word; Shift: TShiftstate);
  202. var x,y: integer;
  203. begin
  204. x:=self.CaretPos.X;
  205. y:=self.CaretPos.Y;
  206. OldBlank := ((copy(Lines[y],1,x) = stringofchar(' ',x)));
  207. inherited;
  208. Toggle:=true;
  209. if key = 9 then key:=0;
  210. KeyUp(Key, Shift);
  211. Toggle:=false;
  212. end;
  213.  
  214.  
  215. procedure TRichMemo.Click;
  216. begin
  217. OldY:=CaretPos.Y;
  218. inherited;
  219. end;
  220.  
  221. procedure TRichMemo.DoEnter;
  222. begin
  223. Toggle:=true;
  224. OldY:=CaretPos.Y;
  225. inherited;
  226. end;
  227.  
  228. end.

Notes.

(1) The TRichMemo should be used with "want tabs" on. (We replace the tabs with four spaces because this is a Python editor but we need "want tabs" to detect the event properly.)

(2) This code responds automatically to user input but not to the program doing stuff. For this reason I've equipped it with a public procedure TouchLine which will refresh a given line. This could all I presume be done better by subclassing TStrings and then messing with TRichMemo, I might do that eventually but this will do for now.

(3) RichMemo completely swallows the WM_PASTE event. There must be people who know a good way round this. My way is somewhat hacky, and just depends on detecting whether the cursor has travelled forward through several lines without a mouse event making it happen. In that case we assume the user has pasted and refresh the highlighting.

(4) The code is written to a constraint which you can't see, since it's designed so the component will interact with key event handlers in the exact way I think it should. Calling KeyUp from KeyDown does that for me, and then the Toggle variable and OldBlank are kludges to get me out of all the trouble that doing that causes.

It may be that what I've done exactly suits your needs too. But if you find it gets in your way, and makes KeyDown or KeyUp event handlers work how you don't want, try taking out the call to KeyUp in the KeyDown procedure in my code, or indeed the entire procedure. The position of the inherited keyword in the KeyUp and KeyDown procedures will also affect how they interact with KeyUp and KeyDown events.

--- I've modified it a few times and I thing I've got all the bugs out ... I shall be dogfooding it extensively I hope.

--- I've changed the code so it works with word wrap on ... not that you'd want to for code but there are other applications for highlighting.
Title: Re: Using the code from Inno Setup to use Scintilla
Post by: Spoonhorse on June 17, 2021, 10:24:33 am
Actually there is one thing I still need help with if anyone's out there. When you read a text file into a TMemo by using Add to add one line at a time, it trickles in one line at a time, that's how it's displayed, it makes loading it sluggish.

Form1.BeginFormUpdate does nothing.

LockWindowUpdate(Form1.Memo1.Handle) doesn't help. It has the same effect but as though the text was white-on-white ... but you still see it scrolling down.

Making the TMemo invisible does nothing. Go figure.

Then, inspiration! Load it all into one big string and then set the .Text field of the TMemo to be that. Much better.

BUT, if I do that to my TRichMemo, and then tell it to ue my procedure TouchLine on every line to do the highlighting ... then it only highlights halfway though longish files and then gives up. I can't understand why.
TinyPortal © 2005-2018