Miscellanées, le site de Christian Féron
Le site de Christian Féron

SynEdit, quelques trucs et astuces

par Christian Féron - 2016

SynEdit est un composant logiciel pour Delphi et Free Pascal Lazarus. C'est un éditeur de code pouvant être ajouté à n'importe quelle application Delphi et FPL. Il est doté d'une coloration syntaxique pour la plupart des langages informatiques : HTML, CSS, Javascript, C++, Pascal, assembleur, Perl...

Il ne comporte pas de dépendance telles que des dll, et s'intègre facilement dans un logiciel existant.

Exemple SynEdit

Exemple de coloration syntaxique avec SynEdit

Le seul reproche que l'on puisse faire à SynEdit est que la version pour Delphi n'est plus maintenue depuis septembre 2013, alors que la version pour FPL l'est toujours.

Toutefois, la dernière version de SynEdit pour Delphi supporte l'Unicode. De plus, le code source étant fourni, il reste possible d'intervenir sur toutes les parties du composant, ainsi que d'ajouter des highlighters pour de nouveaux langages.

Où télécharger SynEdit ?

Il existe deux versions majeures, l'une sans unicode pour les très anciennes versions de Delphi, l'autre en unicode pour les versions plus récentes :

  • SynEdit-2_0_6 : sans unicode, de Delphi 3 jusqu'à Delphi 2006.
  • Version SynEdit-2_0_8 : avec unicode, de Delphi 5 jusqu'à XE 5 et plus.

Je vous conseille d'utiliser la version unicode si vous le pouvez.

Liens de téléchargement :
https://sourceforge.net/projects/synedit/files/1%20-%20Stable%20Releases/
SynEdit-2_0_6 (copie de secours)
SynEdit-2_0_8 (copie de secours)

Installation du composant

Dans Delphi, cliquez sur Fichier/Ouvrir, allez dans le dossier SynEdit/Packages et ouvrez le projet correspondant à votre version de Delphi. Il vous restera enfin à compiler et installer le paquet. Sur Free Pascal Lazarus, SynEdit est pré-installé d'origine.

Pour installer SynEdit Unicode dans Delphi 2005 édition personnelle, utilisez SynEdit_D7_PE.dpk dans le dossier SynEdit/Packages.

Lorsque le SynEdit a été correctement installé, il est disponible dans la palette de composants lorsque vous êtes en mode conception.

Problèmes divers de SynEdit pour Delphi


Voici les solutions de quelques soucis que j'ai rencontré dans l'utilisation de SynEdit Unicode pour Delphi, avec Delphi 2005 édition personnelle.

Problème d'ouverture de fichiers UTF8 sans BOM

Si l'on veut ouvrir un fichier UTF8 sans Byte Order Mark, il sera ouvert en tant que fichier Ansi, ce qui provoquera l'affichage de « signes bizarres ». Ce défaut pourra être facilement corrigé en rectifiant l'unité SynUnicode.Pas dans la procédure LoadFromStream, comme indiqué ci-dessous.

procedure TUnicodeStrings.LoadFromStream(Stream: TStream);
// usual loader routine, but enhanced to handle byte order marks in stream
var
  Size,
  BytesRead: Integer;
  ByteOrderMask: array[0..5] of Byte; // BOM size is max 5 bytes (cf: wikipedia)
                                      // but it is easier to implement with a multiple of 2
  Loaded: Boolean;
  SW: UnicodeString;
  SA: AnsiString;
begin
  BeginUpdate;
  try
    Loaded := False;

    Size := Stream.Size - Stream.Position;
    BytesRead := Stream.Read(ByteOrderMask[0], SizeOf(ByteOrderMask));

    // UTF16 LSB = Unicode LSB/LE
    if (BytesRead >= 2) and (ByteOrderMask[0] = UTF16BOMLE[0])
      and (ByteOrderMask[1] = UTF16BOMLE[1]) then
    begin
      FSaveFormat := sfUTF16LSB;
      SetLength(SW, (Size - 2) div SizeOf(WideChar));
      Assert((Size and 1) <> 1, 'Number of chars must be a multiple of 2');
      if BytesRead > 2 then
      begin
        System.Move(ByteOrderMask[2], SW[1], BytesRead - 2); // max 4 bytes = 2 widechars
        if Size > BytesRead then
          // first 2 chars (maximum) were copied by System.Move
          Stream.Read(SW[3], Size - BytesRead);
      end;
      SetTextStr(SW);
      Loaded := True;
    end;

    // UTF16 MSB = Unicode MSB/BE
    if (BytesRead >= 2) and (ByteOrderMask[0] = UTF16BOMBE[0])
      and (ByteOrderMask[1] = UTF16BOMBE[1]) then
    begin
      FSaveFormat := sfUTF16MSB;
      SetLength(SW, (Size - 2) div SizeOf(WideChar));
      Assert((Size and 1) <> 1, 'Number of chars must be a multiple of 2');
      if BytesRead > 2 then
      begin
        System.Move(ByteOrderMask[2], SW[1] ,BytesRead - 2); // max 4 bytes = 2 widechars
        if Size > BytesRead then
          // first 2 chars (maximum) were copied by System.Move
          Stream.Read(SW[3], Size - BytesRead);
        StrSwapByteOrder(PWideChar(SW));
      end;
      SetTextStr(SW);
      Loaded := True;
    end;

    // UTF8
    if (BytesRead >= 3) and (ByteOrderMask[0] = UTF8BOM[0])
      and (ByteOrderMask[1] = UTF8BOM[1]) and (ByteOrderMask[2] = UTF8BOM[2]) then
    begin
      FSaveFormat := sfUTF8;
      SetLength(SA, (Size - 3) div SizeOf(AnsiChar));
      if BytesRead > 3 then
      begin
        System.Move(ByteOrderMask[3], SA[1], BytesRead - 3); // max 3 bytes = 3 chars
        if Size > BytesRead then
          // first 3 chars were copied by System.Move
          Stream.Read(SA[4], Size - BytesRead);
        SW := UTF8Decode(SA);
      end;
      SetTextStr(SW);
      Loaded := True;
    end;
    
    
    // UTF8 no-BOM or Ansi ----- Bug fixed, not origin of SynEdit -----
    if not Loaded then
    begin
      FSaveFormat := sfAnsi;
      SetLength(SA, Size div SizeOf(AnsiChar));
      if BytesRead > 0 then
      begin
        System.Move(ByteOrderMask[0], SA[1], BytesRead); // max 6 bytes = 6 chars
        if Size > BytesRead then
          Stream.Read(SA[7], Size - BytesRead); // first 6 chars were copied by System.Move
        SW := UTF8Decode(SA);
        if SW <> '' then begin   //UTF8 no-BOM
          FSaveFormat := sfUTF8;
          SetTextStr(SW);
          Loaded := True;
        end;
      end;
      if not Loaded then        //Ansi
        SetTextStr(SA);
    end;
    

    // default case (Ansi)    ----- Replaced -----
    {if not Loaded then
    begin
      FSaveFormat := sfAnsi;
      SetLength(SA, Size div SizeOf(AnsiChar));
      if BytesRead > 0 then
      begin
        System.Move(ByteOrderMask[0], SA[1], BytesRead); // max 6 bytes = 6 chars
        if Size > BytesRead then
          Stream.Read(SA[7], Size - BytesRead); // first 6 chars were copied by System.Move
      end;
      SetTextStr(SA);
    end; }                  
  finally
    EndUpdate;
  end;
end;

J'ai trouvé cette solution ici : https://forum.mh-nexus.de/viewtopic.php?t=371. A noter la prise de bec entre deux intervenants dans les commentaires, mais qui n'avait pas de raison d'être, puisque j'ai personnellement testé ce code et il donne satisfaction.

Il y a un petit ralentissement si le fichier est au format Ansi, mais ce ralentissement est négligeable et ne gêne pas. Cette solution est donc tout à fait applicable.

Problème de sélection d'un mot entier contenant des lettres accentuées

Par exemple, le mot « carrière » est sélectionné du début jusqu'à la lettre accentuée, ou bien après la lettre accentuée et jusqu'à la fin, alors qu'il devrait être sélectionné en totalité.

En cherchant avec Google, j'ai trouvé ces deux solutions :

L'alternative ci-dessus ne m'a pas donné entière satisfaction. Malgré tout, je remercie les deux contributeurs qui m'ont mis sur la bonne piste, ce qui m'a fait gagner beaucoup de temps pour trouver une meilleure solution, que j'ai indiqué ci-dessous.

Rectifiez dans l'unité SynHighlighterHtml.pas à l'intérieur de la procédure IsIdentChar.

function TSynHTMLSyn.IsIdentChar(AChar: WideChar): Boolean;
begin
  case AChar of
    '_', '/', '0'..'9', 'A'..'Z', 'a'..'z',
    'À'..'Ö', 'Ù'..'Ý', 'à'..'ö', 'ù'..'ý', 'ÿ', 'ƒ': //Christian Feron 08/05/2016
      Result := True;
    else
      Result := False;
  end;
end;

Cette modification devrait, normalement, être appliquée sur tous les highlighters qui acceptent les commentaires. Mais, si vous n'utilisez qu'un ou deux highlighters seulement, vous pouvez vous contenter de modifier uniquement ceux-là.

Comment détecter l'encodage d'un fichier avec SynEditUnicode

Vous voulez ouvrir un fichier texte, et savoir quel est son format : Ansi, UTF8 sans BOM, UTF8 avec BOM, UTF16 Little Endian, UTF16 Big Endian. Dans ce cas, utilisez la fonction GetEncoding de l'unité SynUnicode.pas, comme dans l'exemple ci-dessous.

procedure TForm1.GetFileEncoding(aFileName: String);
var
  aEncoding : TSynEncoding;
  aBOM : Boolean;
begin
 aEncoding := GetEncoding(aFileName, aBOM);//Identifies file encoding

 case aSynEncoding of
   seAnsi : Label1.Caption := 'Ansi';
   seUTF8 : begin
              if not aBOM then Label1.Caption := 'UTF8';
              if ABOM then Label1.Caption := 'UTF8Bom';
            end;
   seUTF16LE : Label1.Caption := 'LittleEndian';
   seUTF16BE : Label1.Caption := 'BigEndian';
 end;//End case...
end;

Pour utiliser plusieurs highlighters dans un même document

Un document HTML peut contenir à la fois du HTML, du CSS et du Javascript. Comment faire pour obtenir une coloration syntaxique sur les trois à la fois ?

Pour cela, il faut utiliser le TSynMultiSyn, disponible dans la palette principale des composants SynEdit (pas celle où se trouvent tous les highlighters, mais l'autre).

Lorsque vos différents highlighters ont été placés sur votre TForm, vous pouvez les initialiser dans une procédure, par exemple comme ceci :

procedure TMainForm.InitMultiHighlighters;
begin
// HTML complex
 with SynMultiSyn1 do begin //----- Multi-Highlighters ----------------------
  DefaultHighlighter := SynHTMLSyn1;
  DefaultLanguageName:= 'HTML complex';
  DefaultFilter      := 'HTML complex (*.html; *.htm)|*.html; *.htm';
  with Schemes do begin
    Add.Index:= 0; //----- Javascript -----
    with Items[0] do begin
      SchemeName := 'Sch_Javascript';
      Highlighter := SynJScriptSyn1;
      StartExpr  := '<script';
      EndExpr    := '</script>';
      with MarkerAttri do begin
        Background := $00CDEBFF;//PeachPuff
        Foreground := clNavy;
      end;
    end;//End with Items[0]...
    Add.Index:= 1; //----- CSS -----
    with Items[1] do begin
      SchemeName := 'Sch_CSS';
      Highlighter := SynCssSyn1;
      StartExpr  := '<style';
      EndExpr    := '</style>';
      with MarkerAttri do begin
        Background := $00CDEBFF;//PeachPuff
        Foreground := clNavy;
      end;
    end;//End with Items[1]...
    
  end;//End with Schemes...
 end;//End with SynMultiSyn1...
end;

Vous pouvez aussi regarder l'exemple visible ici :
https://github.com/SynEdit/SynEdit/tree/master/Demos/MultiSynDemo

Pour créer un nouveau highlighter dans SynEdit

Regardez dans le dossier SynEdit/SysGen, vous trouverez le code-source du générateur de highlighters ainsi que son mode d'emploi en HTML.

Si vous n'arrivez pas à compiler le générateur, utilisez celui-ci, que j'ai compilé avec Delphi 2005 PE : SynGen.exe

Interface de SynGen.exe

Interface de SynGen.exe

Utiliser ce générateur suppose que vous ayez déjà créé un fichier de grammaire pour votre nouveau highlighter (*.msg) bien évidemment.