Les coulisses du Techdays 2014 : Colorisation de code

Téléchargement du code.

20 Décembre 2013, J-53 avant l’édition des Techdays 2014. J’ai pris un peu de retard sur mes prévisions, car j’ai travaillé en parallèle, sur un projet autour de l’Oculus Rift qui m’a pris un peu de temps. J’y reviendrais dans un prochain billet, et vous devriez en voir le résultat aux Techdays.

Comme je le précisais dans mon précédant billet, je m’attèle à développer une application afin d’illustrer les nouvelles APIS disponibles avec le Windows Runtime 8.1.

Dans ce 1er billet j’expliquais comment intégrer MEF 2.0, afin de pouvoir charger dynamiquement chaque exemple.

Dans ce second exemple nous allons aborder la coloration syntaxique des exemples.

Pour que les exemples soient plus parlant, je veux pouvoir afficher le code des exemples, et pour qu’ils soient le plus lisible possible réutiliser les jeux de couleurs par défaut de C# et XAML.

Comme illustrer sur la figure suivante pour du code CSharp

image

Ou pour du code XAML

image

Pour ce faire, j’ai donc développé un composant C++ qui :

Pour du code C#, parse une 1er fois le texte pour en extraire tous les jetons, puis je fais une seconde passe qui formate le texte au format RTF. (J’ai choisi ce format, car je peux ainsi sauvegarder un document que je peux relire avec Word sans perte d’informations).

Alors comment ça marche ?

Tout d’abord je tiens à préciser que je n’ai utilisé aucune méthode connue ou reconnue pour faire de l’analyse syntaxique ni de lex & yacc ou Expressions régulières

Je lis tout bêtement ligne par ligne, puis caractère par caractère , l’intégralité du texte (oui je sais c’est pas bô), mais avec la stl, c’est rapide !!

L’intégralité des jetons sont sauvegardés dans des vecteurs de types stl, que je réutilise dans une seconde méthode qui formate tous les jetons avec la couleur souhaitée.

Néanmoins, comme je n’ai pas la prétention de refaire ce qui est fait dans l’éditeur Visual Studio, je m’appui également sur un fichier de ressources qui contient les éléments de base pour formater correctement en RTF, ainsi que les couleurs que je souhaite affecter à chaque jetons. Ce fichier de ressources, contient également un certain nombre de mots clés au langage pré-formaté à la bonne couleur (bleu par exemple pour les mots clés standard du langage). Il est donc possible à tout moment de rajouter un mot clé, ou de modifier une couleur si les couleurs de bases ne conviennent pas.

Pour de plus amples informations sur le format RTF c’est ici.

Voici comment ce fichier se présente :

Code Snippet

  1. <RTFDoc>
  2.   <OpenRtfTag Value="{\rtf1\ansi"/>
  3.   <CloseRtfTag Value="}" />
  4.   <ColorTable Value="{\colortbl;\red0\green0\blue255;\red0\green255\blue0;\red43\green145\blue175;\red0\green0\blue0;\red163\green21\blue21;\red0\green128\blue0;}}"/>
  5.   <FontTable Value="{\fonttbl {\f0 Consolas;}"/>
  6.   <Paragraphe Value="\par"/>
  7.   <ColorComment Value="\f0\cf6"/>
  8.   <ColorCharsString Value="\f0\cf5"/>
  9.   <Black Value="\f0\cf4"/>
  10.   <ColorKeyWord Value="\f0\cf1"/>
  11.   <ColorType Value="\f0\cf3"/>
  12.   <namespace Value="\f0\cf1"/>
  13.   <using Value="\f0\cf1"/>
  14.   <class Value="\f0\cf1"/>
  15.   <interface Value="\f0\cf1"/>
  16.   <public Value="\f0\cf1"/>
  17.   <static Value="\f0\cf1"/>
  18.   <internal Value="\f0\cf1"/>
  19.   <protected Value="\f0\cf1"/>
  20.   <override Value="\f0\cf1"/>
  21.   <private Value="\f0\cf1"/>
  22.   <sealed Value="\f0\cf1"/>
  23.   <partial Value="\f0\cf1"/>
  24.   <readonly Value="\f0\cf1"/>
  25.   <typeof Value="\f0\cf1"/>
  26.   <add Value="\f0\cf1"/>
  27.   <remove Value="\f0\cf1"/>
  28.   <lock Value="\f0\cf1"/>
  29.   <yield Value="\f0\cf1"/>
  30.   <object Value="\f0\cf1"/>
  31.   <Object Value="\f0\cf3"/>
  32.   <async Value="\f0\cf1"/>
  33.   <await Value="\f0\cf1"/>
  34.   <new Value="\f0\cf1"/>
  35.   <var Value="\f0\cf1"/>
  36.   <for Value="\f0\cf1"/>
  37.   <do Value="\f0\cf1"/>
  38.   <try Value="\f0\cf1"/>
  39.   <catch Value="\f0\cf1"/>
  40.   <finally Value="\f0\cf1"/>
  41.   <while Value="\f0\cf1"/>
  42.   <return Value="\f0\cf1"/>
  43.   <get Value="\f0\cf1"/>
  44.   <set Value="\f0\cf1"/>
  45.   <value Value="\f0\cf1"/>
  46.   <const Value="\f0\cf1"/>
  47.   <ref Value="\f0\cf1"/>
  48.   <event Value="\f0\cf1"/>
  49.   <delegate Value="\f0\cf1"/>
  50.   <Delegate Value="\f0\cf3"/>
  51.  
  52.   <if Value="\f0\cf1"/>
  53.   <foreach Value="\f0\cf1"/>
  54.   <else Value="\f0\cf1"/>
  55.   <in Value="\f0\cf1"/>
  56.   <int Value="\f0\cf1"/>
  57.   <bool Value="\f0\cf1"/>
  58.   <Boolean Value="\f0\cf3"/>
  59.   <false Value="\f0\cf1"/>
  60.   <true Value="\f0\cf1"/>
  61.  
  62.   <double Value="\f0\cf1"/>
  63.   <char Value="\f0\cf1"/>
  64.   <Char Value="\f0\cf3"/>
  65.   <single Value="\f0\cf1"/>
  66.   <long Value="\f0\cf1"/>
  67.   <string Value="\f0\cf1"/>
  68.   <String Value="\f0\cf3"/>
  69.  
  70.   <byte Value="\f0\cf1"/>
  71.   <enum Value="\f0\cf1"/>
  72.   <readonly Value="\f0\cf1"/>
  73.   <as Value="\f0\cf1"/>
  74.   <base Value="\f0\cf1"/>
  75.   <break Value="\f0\cf1"/>
  76.   <case Value="\f0\cf1"/>
  77.   <checked Value="\f0\cf1"/>
  78.   <default Value="\f0\cf1"/>
  79.   <extern Value="\f0\cf1"/>
  80.   <explicit Value="\f0\cf1"/>
  81.   <out Value="\f0\cf1"/>
  82.   <operator Value="\f0\cf1"/>
  83.   <params Value="\f0\cf1"/>
  84.   <sbyte Value="\f0\cf1"/>
  85.   <short Value="\f0\cf1"/>
  86.   <uint Value="\f0\cf1"/>
  87.   <ulong Value="\f0\cf1"/>
  88.   <unchecked Value="\f0\cf1"/>
  89.   <unsafe Value="\f0\cf1"/>
  90.   <virtual Value="\f0\cf1"/>
  91.   <volatile Value="\f0\cf1"/>
  92.   <decimal Value="\f0\cf1"/>
  93.   <do Value="\f0\cf1"/>
  94.   <switch Value="\f0\cf1"/>
  95.   <is Value="\f0\cf1"/>
  96.   <stackalloc Value="\f0\cf1"/>
  97.   <goto Value="\f0\cf1"/>
  98.   <short Value="\f0\cf1"/>
  99.   <select Value="\f0\cf1"/>
  100.   <into Value="\f0\cf1"/>
  101.   <orderby Value="\f0\cf1"/>
  102.   <from Value="\f0\cf1"/>
  103.   <yield Value="\f0\cf1"/>
  104.   <join Value="\f0\cf1"/>
  105.   <where Value="\f0\cf1"/>
  106.   <group Value="\f0\cf1"/>
  107.   <let Value="\f0\cf1"/>
  108.   <Byte Value="\f0\cf3"/>
  109.   <Type Value="\f0\cf3"/>
  110.   <void Value="\f0\cf1"/>
  111.   <this Value="\f0\cf1"/>
  112.   <null Value="\f0\cf1"/>
  113.     <throwValue="\f0\cf1"/>
  114.   <NumberSign Value="\f0\cf1"/>
  115.   <UInt32 Value="\f0\cf3"/>
  116.   <Int32 Value="\f0\cf3"/>
  117.   <Int16 Value="\f0\cf3"/>
  118.   <Int64 Value="\f0\cf3"/>
  119.   <IntPtr Value="\f0\cf3"/>
  120.   <UIntPtr Value="\f0\cf3"/>
  121.   <UInt16 Value="\f0\cf3"/>
  122.   <UInt64 Value="\f0\cf3"/>
  123.   <IBuffer Value="\f0\cf3"/>
  124.   <LeftCurlyBracket Value="\f0\cf4\{" />
  125.   <RightCurlyBracket Value="\f0\cf4\}" />
  126. </RTFDoc>

Ce fichier est composé d’un formatage minimum

Un attribut permettant d’ouvrir le format RTF - OpenRtfTag

Un attribut permettant de fermer le format RTF - CloseRtfTag

Un attribut ColorTable définissant la table des couleurs

Un attribut FontTable définissant la table des fontes

L’attribut ColorType pour la couleur des types

Les autres attributs définissent soit des couleurs pour des mots clés, soit pour des commentaires (ColorComment), soit pour illustrer une chaine de caractères (ColorCharsString)

Vite fait comment ça marche le format RTF.

Par exemple l’attribut ColorComment déclare la valeur “\f0\cf6” cela veut dire que l’on va prendre :

La fonte position 0 dans la table des fontes en l’occurrence Consolas

La couleur en position 6, soit red0\green128\blue0, le fameux vert caractéristiques des commentaires dans Visual Studio.

Et ainsi de suite pour les autres attributs

En C# pour appeler mon composant c’est simple comme illustrer dans le code suivant :

Code Snippet

  1. char[] separator = { '\n', '\r' };
  2.                  String[] lines = allText.Split(separator, StringSplitOptions.RemoveEmptyEntries);
  3.                  Ultimate.Parser.IParse cs = new Ultimate.Parser.CSharp();
  4.                  rtf = await cs.ParseCodeAsync(lines, new Uri("ms-appx:///Resources/RTFCSFormat.xml"));

La méthode ParseCodeAsync() , prend comme 1er paramètre un tableau contenant chaque lignes de code C#, ainsi qu’une URI pointant sur mon fichier de ressources et retourne une chaine de caractères au format RTF. A partir de là il suffit soit d’afficher le résultat dans un RichTextBox, soit de le sauvegarder dans un fichier.

La méthode ParseCodeAsync() en faite, ne fait pas grand chose, elle sert uniquement d’intermédiaire entre C# et C++ car tout le code de parsing est en ISO C++, et non pas en C++/CX propre au Windows Runtime.

Code Snippet

  1.  
  2. IAsyncOperation<Platform::String^>^ CSharp::ParseCodeAsync(const Platform::Array<Platform::String^>^ codetoparse,
  3.             Windows::Foundation::Uri^ uri)
  4. {
  5.     return create_async([&uri,codetoparse, this]()
  6.     {
  7.             this->ParseTokens(codetoparse);
  8.             return this->FormatRTFAsync(uri);        
  9.     });                
  10. }

Pour la colorisation de code xaml, c’est peu ou prou la même chose, à l’exception du fait que je délègue le parsing des jetons à la librairie XMLLite qui à besoin en entrée d’un flux de données, plutôt qu’un tableau de chaine de caractères.

Voici en C# comment on y fait appel :

Code Snippet

  1. Ultimate.Parser.IParse xaml = new Ultimate.Parser.XAML();
  2.                 var xamlFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("tempofile.xaml", CreationCollisionOption.ReplaceExisting);
  3.                 await Windows.Storage.FileIO.WriteTextAsync(xamlFile,allText);
  4.  
  5.                 rtf = await xaml.ParseCodeAsync(xamlFile, new Uri("ms-appx:///Resources/RTFXAMLFormat.xml"));
  6.                 

Et voici la définition de la méthode C++/CX

Code Snippet

  1. IAsyncOperation<Platform::String^>^ XAML::ParseCodeAsync(Windows::Storage::StorageFile^ file,
  2.     Windows::Foundation::Uri^ uri)
  3. {
  4.     return create_async([file,&uri, this]()
  5.     {
  6.         task<StorageFile^>    getFileTask(Windows::Storage::StorageFile::GetFileFromApplicationUriAsync(uri));
  7.         return getFileTask.then([file,this](StorageFile^ ressourcefile)
  8.         {
  9.             return ressourcefile->OpenAsync(FileAccessMode::Read);
  10.         }, task_continuation_context::use_arbitrary()).then([file, this](IRandomAccessStream^ stream)
  11.         {            
  12.             m_common = std::unique_ptr<Common>(new Common());
  13.             HRESULT hr = m_common.get()->InitXmlReader(stream);
  14.             if (FAILED(hr))
  15.             {
  16.                 throw ref new Platform::Exception(E_FAIL, ref new String(L"Erreur lors de la lecture du fichier de ressources XML"));
  17.             }            
  18.         }, task_continuation_context::use_arbitrary()).then([file, this]()
  19.         {            
  20.             return file->OpenAsync(FileAccessMode::Read);
  21.         }, task_continuation_context::use_arbitrary()).then([this](IRandomAccessStream^ stream)
  22.         {
  23.             std::wstring rtf = this->FormatXAMLRTF(stream);
  24.             StringReference sr(rtf.c_str());
  25.             return sr.GetString();            
  26.         });
  27.     });                    
  28. }

J’ai volontairement omis le code de parsing, mais que vous pourrez retrouver ici.

Dans le prochain billet nous aborderons la partie la plus sensible, c’est à dire la sauvegarde du code dans une base de données au format ESE.

Eric