Gambas France BETA


Pas de compte ? Incription

[Tuto] L'objet Editor et la coloration syntaxique

Ce sujet est résolu.

1
AuteurMessages
Prokopy#1 Posté le 23/7/2010 à 19:54:00
Kinder PinguiBonjour à tous,

Je me suis penché sur le fonctionnement du contrôle Editor, présent dans le composant gb.qt4.ext (indisponible pour GTK+ donc), et en particulier sur la coloration syntaxique personnalisée. Après un petit tour des exemples et de la doc, je me suis aperçu que sans une bonne organisation, ça devient vite le bordel. :P
Je vais donc vous donner quelques petits conseils ainsi que ma méthode pour garder tout votre beau code bien propre. Je ne dis pas que c'est la meilleure méthode, mais je trouve qu'elle fonctionne bien pour ce que j'ai à faire. ;)


Rappels et démarrage :

Tout d'abord, quelques petits rappels concernant le contrôle Editor.
Cet objet possède une propriété Highlight, qui définit le type de coloration appliquée à l'éditeur. Les valeurs que peuvent prendre cette propriété sont quelques unes des constantes de la classe Highlight. Vous avez la possibilité de colorer du Gambas, du HTML, etc. Mais nous autres masochistes, comme on aime bien tout faire nous-mêmes, on va utiliser la constante Highlight.Custom, qui permet d'implémenter son propre gestionnaire de coloration syntaxique. :twisted:

Maintenant, comment faire notre fonction de coloration syntaxique ?
En fait, le contrôle Editor nous met aussi à disposition un évènement Highlight, qui est appelé à chaque fois qu'une ligne de code a besoin d'être colorée. Le contenu de cette ligne est situé dans la propriété Text de la classe Highlight. Comme j'ai la flemme de réécrire Highlight.Text à chaque fois, je crée une variable au nom plus court et plus clair :tongue: :

1
DIM texte AS STRING = Highlight.Text


Pour finir, reste à voir comment colorer notre texte. Il faut pour cela utiliser la méthode Add() de la classe Highlight. Cette méthode colore le texte caractère par caractère en lui affectant le type de coloration passé en paramètre, c'est-à-dire si c'est un mot-clé, un opérateur, un commentaire, etc. Les valeurs à passer sont aussi des constantes de la classe Highlight.
Par exemple, si je veux dire que les deux premiers caractères de la ligne à colorer soint colorés comme des mots-clés, je devrai faire :

1
2
Highlight.Add(Highlight.Keyword)
Highlight.Add(Highlight.Keyword)


Voilà pour les bases. Passons maintenant à un peu de pratique. :)

Ma méthode :

Je trouve la solution de tout analyser caractère par caractère dans une boucle et d'appeler Highlight.Add() à la fin un peu bordélique (solution utilisée dans l'exemple fourni avec Gambas). J'ai vu qu'ils s'embêtaient un peu à analyser le précédent caractère, voir si ça colle avec une balise / texte / etc. Bref c'est très énèrvant.
J'ai trouvé une autre méthode : celle d'utiliser un tableau d'integer de la longueur du texte, qu'on remplirait avec les constantes de Highlight et qu'on énumèrerait à la fin dans une boucle pour mettre dans la fonction Highlight.Add().

En bref, je préfère faire comme ça :

1
2
3
4
5
6
7
8
9
10
PUBLIC SUB Editeur_Highlight()
DIM texte AS STRING = Highlight.Text
DIM surlignages AS NEW INTEGER[String.Len(texte)]
DIM i AS INTEGER
surlignages.Fill(Highlight.Normal) 'On pré-remplit le tableau pour qu'au début, tout soit sans coloration
'On va faire nos machins ici
FOR i = 0 TO surlignages.Max
Highlight.Add(surlignages[i])
NEXT
END


Il y a une raison simple à cette préférence. Vous connaissez la méthode Fill() ? Vous savez, la méthode qui permet de remplir les tableaux ! Et vous n'êtes pas non plus sans savoir qu'elle prend en plus deux paramètres : un pour commencer le remplissage à un certain endroit du tableau, et le deuxième pour remplir seulement une certaine longueur.
Et là, pif paf pouf, coup de génie ! Comme la coloration de chaque caractère est représentée dans notre tableau, il faut juste connaître la position et la longueur de notre mot-clé pour pouvoir le colorer ! :idea:
Grâce à cette méthode, il nous suffit de faire donc :

1
surlignages.Fill(Highlight.Keyword, positionMotCle, longueurMotCle)


Et tout devient bien plus simple ! :D

Une coloration simple, les smileys :

Pour ce premier exercice, on va faire une coloration simple : dans notre code, on veut faire en sorte que tous les smileys soient colorés comme si c'étaient des symboles (on va donc utiliser la constante Highlight.Symbol pour colorer).
Si on se base sur le schéma précédent, il nous manque deux choses : la position et la longueur. Pour la longueur, pas de problème, on peut facilement la connaître avec String.Len(). Mais pour la position, comment la retrouver ?


Vous savez pas ? :|
Il y a une méthode de la classe String qui nous renvoie la position d'une sous-chaîne dans une chaîne : InStr().
Petite touche personnelle encore : quand j'utilise des smileys, j'ai l'habitude de metre un espace avant et après. Prenez cette phrase :

Le bateau a coulé:pourquoi ?


Oui, je sais, il faut un espace avant et après les deux-points. Mais si on est dans un cas où il n'y en a pas, notre colorateur va prendre les deux-points et le p comme un smiley, et ça le fait pas terrible…
Donc il vaut mieux s'assurer qu'il y ait un espace avant et après pour éviter ce genre de bêtises. ;)

Eh bien qu'attendons-nous pour essayer ?


1
2
3
4
5
6
7
8
9
10
PUBLIC SUB Editeur_Highlight()
DIM texte AS STRING = Highlight.Text
DIM surlignages AS NEW INTEGER[String.Len(texte)]
DIM i AS INTEGER
surlignages.Fill(Highlight.Normal) 'On pré-remplit le tableau pour qu'au début, tout soit sans coloration
surlignages.Fill(Highlight.Keyword, String.InStr(texte, " :p ") - 1, String.Len(" :p "))
FOR i = 0 TO surlignages.Max
Highlight.Add(surlignages[i])
NEXT
END


J'ai mis InStr() - 1 car pour cette fonction, le premier caractère est 1, alors que la première case d'un tableau est 0. Si on fait pas gaffe on va droit dans le "Dépassement de tableau".

Maintenant, lancez le programme, et essayez de rentrer le petit smiley qui tire la langue (espace - deux-points - p minuscule - espace :P ). Comme d'habitude, l'éditeur de Gambas ne colore pas la ligne courante (vous avez certainement dû le remarquer), allez à la ligne. Super ! Il est coloré !

… Mais juste comme ça, essayez d'en mettre un second sur la même ligne que le premier.
Et le deuxième n'est pas coloré ! Pourquoi ?

C'est simple quand on y réfléchit : InStr() ne renvoie que la première occurrence dans le texte, donc il est normal que seul le premier soit coloré !

Pour pallier au problème, j'ai créé une fonction InStr() à moi que j'ai mis dans une classe "SuperString" et qui renvoie un tableau des positions de toutes les occurrences trouvées. Elle prend les mêmes paramètres que la fonction standard. La voici :

1
2
3
4
5
6
7
8
9
10
STATIC PUBLIC FUNCTION InStr(texte AS STRING, pattern AS STRING, OPTIONAL depuis AS INTEGER = 0) AS Integer[]
DIM positions AS NEW Integer[]
DIM position AS INTEGER
position = String.InStr(texte, pattern, depuis)
WHILE position <> 0
positions.Add(position - 1)
position = String.InStr(texte, pattern, position + String.Len(pattern))
WEND
RETURN positions
END


Vous pouvez vous amuser à comprendre comment ça marche si vous voulez, mais ce n'est pas nécessaire. Retenez juste qu'elle nous renvoie toutes les positions qu'il nous faut (et sans le décalage comme avec le InStr() original), et mettez-la dans une classe SuperString par exemple (ou dans celle que vous êtes en train de faire si vous avez la flemme, mais ça va être le bazar après).

Maintenant, on peut y aller tranquillement :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUBLIC SUB Editeur_Highlight()
DIM texte AS STRING = Highlight.Text
DIM surlignages AS NEW INTEGER[String.Len(texte)]
DIM i, position AS INTEGER
surlignages.Fill(Highlight.Normal) 'On pré-remplit le tableau pour qu'au début, tout soit sans coloration
FOR EACH position IN SuperString.InStr(texte, " :p ")
surlignages.Fill(Highlight.Symbol, position, String.Len(" :p "))
NEXT

FOR i = 0 TO surlignages.Max
Highlight.Add(surlignages[i])
NEXT

END


Tadaa ! Tout marche maintenant ! :cheers:

Une coloration plus complexe, les balises (x)HTML :

Dites-moi, fixons les bases tout de suite, vous n'êtes pas sans savoir qu'une balise HTML commence par < et se termine par >, pas vrai ?
Ouf, vous me rassurez là. :roll:

Le problème avec ces balises, c'est qu'elle peuvent contenir n'importe quoi entre leurs chevrons, et c'est bien là la limite de InStr().

Mais que voulez-vous, Gambas c'est tellement bien foutu qu'il y a toujours une solution : mesdames et messieurs, j'ai nommé les Regex !
(Oui oui, on dit aussi Expressions régulières, mais je trouve ça plus court de dire "les Regex" :tongue: )

Grâce au composant gb.pcre, vous pouvez vous amuser avec les Regex tant que vous voulez ! Le minimum étant bien sûr de connaître son PCRE sur le bout des doigts ! :study:

On se paie un autre problème : les Regex de Gambas ne nous renvoient aussi que la première occurrence trouvée.

Rhalala mais ça me fait une autre fonction à coder ça ! Bon, ben la voilà :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
STATIC PUBLIC FUNCTION RegexpSearch(texte AS STRING, pattern AS STRING) AS Integer[][]

DIM resultats AS NEW Integer[][]
DIM tmptexte AS STRING = texte
DIM tmpPos AS INTEGER = 0
DIM regex AS NEW Regexp(texte, pattern, Regexp.Ungreedy)
WHILE regex.Offset <> -1 AND tmptexte <> ""
regex.Exec(tmptexte)
tmpPos += (regex.Offset + String.Len(regex.Text))
resultats.Add([tmpPos - String.Len(regex.Text), String.Len(regex.Text)])
tmptexte = String.Right(texte, tmpPos * -1)
WEND
RETURN resultats
END


Je l'ai aussi mise dans la classe "SuperString", qui commence à avoir de la geule d'ailleurs. ;)
C'est hyper simple : vous lui passez le texte, votre Regex, et elle vous renvoie un tableau d'Integer à deux dimensions (oui je suis sous GB3, si vous êtes avec GB2 … bah adaptez !) :
Chaque sous-tableau représente une occurrence : dans la première case il y a la position de l'occurence, et dans la seconde sa longueur. Que demander de plus ? 8)

Maintenant, une boucle, une Regex et le tour est joué !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUBLIC SUB Editeur_Highlight()
DIM texte AS STRING = Highlight.Text
DIM surlignages AS NEW INTEGER[String.Len(texte)]
DIM resultat AS Variant[]
DIM i AS INTEGER
surlignages.Fill(Highlight.Normal) 'On pré-remplit le tableau pour qu'au début, tout soit sans coloration
FOR EACH resultat IN SuperString.RegexpSearch(texte, "<(.+)>")
surlignages.Fill(Highlight.Keyword, resultat[0], resultat[1])
NEXT

FOR i = 0 TO surlignages.Max
Highlight.Add(surlignages[i])
NEXT
END


Vous pouvez maintenant colorer une balise en trois lignes de Gambas ! C'est-y pas génial ? :D
Mais … que serait une balise sans ses fidèles attributs ? Eh oui, avec notre système, toute la balise est colorée pareil, attributs compris !

Ben une ou deux petites Regex et le tour est joué !

Commençons avec les valeurs des attributs, c'est facile, c'est tout ce qui est entre guillemets :
1
2
3
4
5
FOR EACH resultat IN SuperString.RegexpSearch(texte, "\".+\"")
IF surlignages[resultat[0]] = Highlight.Keyword
surlignages.Fill(Highlight.String, resultat[0], resultat[1])
ENDIF
NEXT


Pour détecter si on est dans une balise, j'ai juste regardé si la position trouvée avait déjà été considérée comme une balise par notre code précédent. Si oui, alors on est forcément dans une balise !

Pour l'attribut en lui-même, c'est juste une histoire de Regex (j'espère que PCRE et vous êtes copains …) :

1
2
3
4
5
FOR EACH resultat IN SuperString.RegexpSearch(texte, " ([a-zA-Z]+)=")
IF surlignages[resultat[0]] = Highlight.Keyword
surlignages.Fill(Highlight.Operator, resultat[0], resultat[1])
ENDIF
NEXT


Vous noterez que pour ma Regex, j'ai utilisé une classe plutôt que le point, car je vous rappelle qu'un attribut n'est constitué que de lettres. ;)

Voici le petit code final :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
PUBLIC SUB Editeur_Highlight()
DIM texte AS STRING = Highlight.Text
DIM surlignages AS NEW INTEGER[String.Len(texte)]
DIM resultat AS Variant[]
DIM i AS INTEGER
surlignages.Fill(Highlight.Normal) 'On pré-remplit le tableau pour qu'au début, tout soit sans coloration

FOR EACH resultat IN SuperString.RegexpSearch(texte, "<(.+)>")
surlignages.Fill(Highlight.Keyword, resultat[0], resultat[1])
NEXT

FOR EACH resultat IN SuperString.RegexpSearch(texte, "\".+\"")
IF surlignages[resultat[0]] = Highlight.Keyword
surlignages.Fill(Highlight.String, resultat[0], resultat[1])
ENDIF
NEXT

FOR EACH resultat IN SuperString.RegexpSearch(texte, " ([a-zA-Z]+)=")
IF surlignages[resultat[0]] = Highlight.Keyword
surlignages.Fill(Highlight.Operator, resultat[0], resultat[1])
ENDIF
NEXT

FOR i = 0 TO surlignages.Max
Highlight.Add(surlignages[i])
NEXT

END


Vous avez maintenant des balises bien colorées grâce aux Regex !

Mais c'est mooooooche !!!

Comme vous l'avez sans doute remarqué, la coloration utilisée est celle de Gambas, que je trouve très inadaptée à du HTML (ça reste là encore un avis personnel).
La propriété Styles de votre Editor vous permet de faire un peu de ménage. C'est un objet-tableau qui attend comme index une constante de coloration de l'objet Highlight (comme par exemple Highlight.Keyword). Vous tombez alors sur un objet virtuel qui possède des propriétés comme Color (Integer), Bold (Boolean), etc. Je ne vous fais pas de dessin, je pense que vous devinez très bien à quoi ça sert. ;)

Ces propriétés sont à modifier en-dehors de l'évènement Highlight (c'est pas obligé, mais bon). Voici quelque chose qui, en complément du code ci-dessus, rendra vos balises un peu plus belles :

1
2
3
4
5
6
textEditor.Styles[Highlight.Keyword].Color = Color.DarkGreen 'Balises
textEditor.Styles[Highlight.Keyword].Bold = TRUE
textEditor.Styles[Highlight.Operator].Color = Color.DarkGreen 'Attributs
textEditor.Styles[Highlight.Operator].Bold = FALSE
textEditor.Styles[Highlight.String].Color = &aa0000 'Valeurs
textEditor.Styles[Highlight.String].Bold = FALSE


Voilà ! Maintenant vous êtes le roi de la coloration syntaxique ! N'hésitez pas l'utiliser pour nous en mettre plein les n'yeux dans vos programmes ! :king:
La théorie, c'est quand on sait tout et que rien ne fonctionne.
La pratique, c'est quand ça marche mais qu'on ne sait pas pourquoi.
Quand la théorie rejoint la pratique, rien ne fonctionne et on ne sait pas pourquoi.
spheris#2 Posté le 24/7/2010 à 22:11:00
Prokopy,
encore merci pour ce tuto clair et précis.
Je crois qu'il a sa place sur gambasforge et/ou gambaslinux.eg2.fr.
Bravo pour ton travail !
A+
;)
Prokopy#3 Posté le 25/7/2010 à 01:28:00
Kinder PinguiMerci.
Pour ce qui est de GambasForge je l'ai déjà posté, il y a juste un petit problème avec l'envoi des sources en .tar.bz2 et du screenshot, je me prends une erreur 500. Mon user-agent sans doute, je verrai ça quand je serai rentré chez moi. :)
La théorie, c'est quand on sait tout et que rien ne fonctionne.
La pratique, c'est quand ça marche mais qu'on ne sait pas pourquoi.
Quand la théorie rejoint la pratique, rien ne fonctionne et on ne sait pas pourquoi.
jeanyvon#4 Posté le 25/7/2010 à 13:24:00
Gambas? Ma! Et gustoSalut,
ce qui est certain c'est qu'encore une fois la valeur n'attend pas le nombre des années.
merci
JY
Vieillir? On peut retarder mais pas y échapper!
Prokopy#5 Posté le 25/7/2010 à 16:33:00
Kinder PinguiDerien. Tout âge porte ses fruits, il faut savoir les cueillir. :)
La théorie, c'est quand on sait tout et que rien ne fonctionne.
La pratique, c'est quand ça marche mais qu'on ne sait pas pourquoi.
Quand la théorie rejoint la pratique, rien ne fonctionne et on ne sait pas pourquoi.
gambix#6 Posté le 26/7/2010 à 11:05:00
Faire simple !et moi il faudra que je corrige gambasforge :/

mais il faut laisser le temps au temps :)
Moins de texte dans une signature c'est agrandir son espace.
1