Forum > Beginners

Using the code from Inno Setup to use Scintilla

(1/1)

Spoonhorse:
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. 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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TForm1.Button1Click(Sender: TObject);var ScEdit: TScintEdit;beginScEdit:=TScintEdit.Create(Form1);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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---function Scintilla_DirectFunction(ptr: Pointer; iMessage: Cardinal;  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; external 'isscint.dll';
with


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---function Scintilla_DirectFunction(ptr: Pointer; iMessage: Cardinal;  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; external 'edward.bear';
it has exactly the same effect.

Tipped off by this thread 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.)

Spoonhorse:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit PythonMemo; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, RichMemo, RegExpr, Graphics, LazUtf16; type TRule =  record         RegExp: string;         Color: TColor;         Style: TFontstyles;         end; { TRichMemo } TRichMemo = class(RichMemo.TRichMemo)  protected    procedure Change; override;  private    var Count: integer ;        FRules: array[1..80] of TRule;   const     LKEYWORDS :  TStringArray =                ('and', 'assert', 'break', 'class', 'continue', 'def',                'del', 'elif', 'else', 'except', 'exec', 'finally',                'for', 'from', 'global', 'if', 'import', 'in',                'is', 'lambda', 'not', 'or', 'pass', 'print',                'raise', 'return', 'try', 'while', 'yield',                'none', 'true', 'false');     LSYMBOLS :    TStringArray =                 ('=',                 '==', '!=', '<', '<=', '>', '>=',                 '\+', '-', '\*', '/', '\%',                 '\+=', '-=', '\*=', '/=', '\%=',                 '\^', '\|', '\&', '\~', '>>', '<<',                 '\{', '\}', '\[', '\]', '\(', '\)',                 '\.', ',', ';', ':', '\\', '!', '@', '&');  public    constructor Create(AOwner: TComponent); override; end; implementation constructor TRichMemo.Create(AOwner: TComponent);     procedure add(s: string; c: TColor; fs: TFontStyles);      begin      Count:=Count+1;      FRules[Count].RegExp:=s;      FRules[Count].Color:=c;      FRules[Count].Style:=fs;      end; var i: integer; begininherited;Count:=0;for i:=0 to length(LKEYWORDS)-1 do  add('\b'+LKEYWORDS[i]+'\b', clBlack, [fsBold]);for i:=0 to length(LSYMBOLS)-1 do  add(LSYMBOLS[i], clRed, []);add('"[^"\\]*(\\.[^"\\]*)*"', clBlue, []);add('''[^''\\]*(\\.[^''\\]*)*''', clBlue, []);add('\b[+-]?[0-9]+[lL]?\b', clBlue, []);add('\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', clBlue, []);add('\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', clBlue, []);add('#[^\n]*', clGreen, [fsItalic]);Change;end; procedure TRichMemo.Change; //Up(var Key: Word; Shift: TShiftstate);var n: integer;    i: integer;    RE: TregExpr;    off: integer;    TF: TFont;begininherited;TF:=TFont.Create();off:=0;       // Keeps track of the position of the start of the Line of the RichMemo in terms of the underlying text string.for n:=0 to Lines.Count-1 dobegin  if Lines[n]<>'' then // Because TRegExpr can't cope with empty strings.      begin      TF.Name:=Font.Name; TF.Size:=Font.Size; TF.Color:=clBlack; TF.Style:=[];      SetTextAttributes(off,length(Lines[n])+1,TF); // Make everything plain black including the newlines (hence the +1).      RE:=TRegExpr.Create;      for i:=1 to Count do        begin        RE.Expression:=FRules[i].RegExp;        RE.InputString:=self.Lines[n];        if RE.Exec then           repeat           TF.Color:=FRules[i].Color;           TF.Style:=FRules[i].Style;           self.SetTextAttributes(off+RE.MatchPos[0]-1,RE.MatchLen[0],TF)           until not RE.ExecNext;        end;      RE.Free;      end;  off:=off+utf16length(Lines[n])+1; // +1 for the newline.  end;TF.Free;end;  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.

Spoonhorse:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit PythonMemo; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, RichMemo, RegExpr, Graphics, LazUTF16; type TRule =  record         RegExp: string;         Color: TColor;         Style: TFontstyles;         end; { TRichMemo } TRichMemo = class (RichMemo.TRichMemo)  protected    procedure KeyDown(var Key: word; Shift: TShiftState); override;    procedure KeyUp(var Key: word; Shift: TShiftState); override;    procedure Click; override;    procedure DoEnter; override;  private    var Count: integer;        FRules: array[1..80] of TRule;        OldY: integer;        Toggle: Boolean;        OldBlank: boolean;  public    function RichXYtoX(X,Y:integer):integer;    procedure TouchLine(n: integer);  const     LKEYWORDS :  TStringArray =                ('and', 'assert', 'break', 'class', 'continue', 'def',                'del', 'elif', 'else', 'except', 'exec', 'finally',                'for', 'from', 'global', 'if', 'import', 'in',                'is', 'lambda', 'not', 'or', 'pass', 'print',                'raise', 'return', 'try', 'while', 'yield',                'None', 'True', 'False');     LSYMBOLS :    TStringArray =                 ('=',                 '==', '!=', '<', '<=', '>', '>=',                 '\+', '-', '\*', '/', '\%',                 '\+=', '-=', '\*=', '/=', '\%=',                 '\^', '\|', '\&', '\~', '>>', '<<',                 '\{', '\}', '\[', '\]', '\(', '\)',                 '\.', ',', ';', ':', '\\', '!', '@', '&');  public    constructor Create(AOwner: TComponent); override;end; implementation // Calling this RichXYtoX to remind myself that it is NOT compatible with// the regular TMemo, which should have ... (Lines[i])+2;function TRichMemo.RichXYtoX(X,Y:integer): integer;var i:integer;    L:integer;beginL:=utf16length(Text);RichXYtoX:=0;for i:=0 to Y-1 do    begin    RichXYtoX:=RichXYtoX+utf16length(Lines[i]);    if (RichXYtoX < L) and (Text[RichXYtoX+1]=#13) then       begin       RichXYtoX:=RichXYtoX + 1;       end;    end;RichXYtoX:=RichXYtoX+X;end; function firstnonspace(s:string):integer;begin// To be more accurate, it returns the number of leading spaces plus one. So the empty string returns 1.firstnonspace:=1; while (s<>'') and (s[firstnonspace] = ' ') and (firstnonspace<=length(s)) do firstnonspace:=firstnonspace+1;end;  constructor TRichMemo.Create(AOwner: TComponent);     procedure add(s: string; c: TColor; fs: TFontStyles);      begin      Count:=Count+1;      FRules[Count].RegExp:=s;      FRules[Count].Color:=c;      FRules[Count].Style:=fs;      end; var i: integer; begininherited;OldY:=0;Count:=0;OldBlank:=false;Toggle:=false;for i:=0 to length(LKEYWORDS)-1 do  add('\b'+LKEYWORDS[i]+'\b', clBlack, [fsBold]);for i:=0 to length(LSYMBOLS)-1 do  add(LSYMBOLS[i], clRed, []);add('"[^"\\]*(\\.[^"\\]*)*"', clBlue, []);add('''[^''\\]*(\\.[^''\\]*)*''', clBlue, []);add('\b[+-]?[0-9]+[lL]?\b', clBlue, []);add('\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', clBlue, []);add('\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', clBlue, []);add('#[^\n]*', clGreen, [fsItalic]);end;  procedure TRichMemo.TouchLine(n: integer);var RE: TregExpr;    TF: TFont;    off: integer;    i: integer;beginif Lines[n]='' then Exit;off:=RichXYtoX(0,n);TF:=TFont.Create();TF.Name:=Font.Name; TF.Size:=Font.Size; TF.Color:=clBlack; TF.Style:=[];SetTextAttributes(off,utf16length(Lines[n])+1,TF); // Make everything plain black including the newlines (hence the +1).RE:=TRegExpr.Create;for i:=1 to Count do  begin  RE.Expression:=FRules[i].RegExp;  RE.InputString:=self.Lines[n];  if RE.Exec then     repeat     TF.Color:=FRules[i].Color;     TF.Style:=FRules[i].Style;     self.SetTextAttributes(off+RE.MatchPos[0]-1,RE.MatchLen[0],TF)     until not RE.ExecNext;  end;RE.Free;TF.Free;end;  procedure TRichMemo.KeyUp(var Key: Word; Shift: TShiftstate);var i,j: integer;    x,y: integer;    tn: integer;begin x:=self.CaretPos.X;y:=self.CaretPos.Y; //We handle the enter, tab, and backspace keys. if (Key=13) and (y>0) and (Lines[y-1]<>'') then          // enter    begin       if not Toggle then       begin       j:=firstnonspace(Lines[y-1]);       if Lines[y-1][length(Lines[y-1])] = ':' then j:=j+4;       Lines[y]:=StringOfChar(' ',j-1)+Lines[y];       CaretPos := TPoint.Create(j-1,y);       end;    end; if Key=9 then           // tab    begin       if not Toggle then       begin       Lines[y]:=copy(Lines[y],1,x)+'    '+copy(Lines[y],x+1,length(Lines[y])-x);       CaretPos := TPoint.Create(x+4,y);       key:=0;       end;    end; if (Key=8) and (x>0) and (copy(Lines[y],1,x) = stringofchar(' ',x)) and OldBlank then           // backspace    begin       if not Toggle then       begin       tn := 4*((x-1) div 4);    // Because this is how deleting whitespace should work, change the code or sue me.       Lines[y]:=stringofchar(' ',tn)+copy(Lines[y],x+1,length(Lines[y])-x);       CaretPos := TPoint.Create(tn,y);       key:=0;       end;    end; // Then we do the syntax highlighting. x:=self.CaretPos.X;y:=self.CaretPos.Y;if y<=OldY then   TouchLine(y)else   for i:=OldY to y do       TouchLine(i); inherited;end;  procedure TRichMemo.KeyDown(var Key: Word; Shift: TShiftstate);var x,y: integer;beginx:=self.CaretPos.X;y:=self.CaretPos.Y;OldBlank := ((copy(Lines[y],1,x) = stringofchar(' ',x)));inherited;Toggle:=true;if key = 9 then key:=0;KeyUp(Key, Shift);Toggle:=false;end;  procedure TRichMemo.Click;beginOldY:=CaretPos.Y;inherited;end; procedure TRichMemo.DoEnter;beginToggle:=true;OldY:=CaretPos.Y;inherited;end;  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.

Spoonhorse:
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.

Navigation

[0] Message Index

Go to full version