Quand on écrit un texte dans un langage informatique, on améliore sa lisibilité en affichant certains mots ou signes caractéristiques du langage en couleur.
L'objectif de cette page est de décrire comment on fait, en s'appuyant sur l'exemple d'un script SQL (langage de requêtes pour un système de gestion de base de données) conforme à la syntaxe de sqlite3 (http://www.sqlite.org/), affiché dans un QTextEdit.
La colorisation est définie en sous-classant la classe QtGui.QSyntaxHighlighter, et en surchargeant sa méthode highlightBlock.
L'initialisation de cette nouvelle classe consiste à définir une liste de règles, chaque règle étant construite comme un couple [regex, format]:
La méthode highlightBlock va recevoir chaque ligne, va tester chaque règle et en déduire le format à appliquer.
Petite subtilité: l'ordre des règles dans la liste est important: il est évident, par exemple, qu'un mot clé trouvé dans une ligne de commentaire ne doit pas être colorisé comme un mot clé!
Une fois cette classe définie (ColorSyntaxSQL par exemple), la colorisation sera mise en place sur le texte à afficher dans le QTextEdit (self.edit par exemple) par une seule ligne:
self.colorSyntaxSQL = ColorSyntaxSQL(self.edit.document())
On place le script ci-dessous dans un fichier “bibcolorsyntaxsql.py” qui sera importé comme module par le programme utilisateur
#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division #Python 2.7 import os, sys from PyQt4 import QtCore, QtGui ############################################################################# class ColorSyntaxSQL(QtGui.QSyntaxHighlighter): #======================================================================== def __init__(self, parent=None): super(ColorSyntaxSQL, self).__init__(parent) # liste des règles: [[regex, format], [regex, format], ...] self.regles = [] #-------------------------------------------------------------------- # coloration des mots clés SQL de sqlite3 motcles_format = QtGui.QTextCharFormat() motcles_format.setForeground(QtCore.Qt.blue) # mots clés en bleu motcles_format.setFontWeight(QtGui.QFont.Bold) # pour mise en gras # liste des mots à considérer motcles_motifs = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF", "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL", "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA", "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WITH", "WITHOUT"] motcles_motifs += ["TEXT", "INTEGER", "REAL", "NUMERIC", "NONE", "BLOB"] motcles_motifs += ["TRUE", "FALSE"] # enregistrement dans la liste des règles for motcles_motif in motcles_motifs: motcles_regex = QtCore.QRegExp("\\b" + motcles_motif + "\\b", QtCore.Qt.CaseInsensitive) self.regles.append([motcles_regex, motcles_format]) #-------------------------------------------------------------------- # nombre entier ou flottant nombre_format = QtGui.QTextCharFormat() nombre_format.setForeground(QtCore.Qt.darkGreen) nombre_motif = "\\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\\b" nombre_regex = QtCore.QRegExp(nombre_motif) nombre_regex.setMinimal(True) self.regles.append([nombre_regex, nombre_format]) #-------------------------------------------------------------------- # chaine de caractères simple quote: '...' chaine1_format = QtGui.QTextCharFormat() chaine1_format.setForeground(QtCore.Qt.green)#red) chaine1_motif = "\'.*\'" chaine1_regex = QtCore.QRegExp(chaine1_motif) chaine1_regex.setMinimal(True) self.regles.append([chaine1_regex, chaine1_format]) #-------------------------------------------------------------------- # chaine de caractères double quotes: "..." chaine2_format = QtGui.QTextCharFormat() chaine2_format.setForeground(QtCore.Qt.red) chaine2_motif = '\".*\"' chaine2_regex = QtCore.QRegExp(chaine2_motif) chaine2_regex.setMinimal(True) self.regles.append([chaine2_regex, chaine2_format]) #-------------------------------------------------------------------- # delimiteur: parenthèses, crochets, accolades delimiteur_format = QtGui.QTextCharFormat() delimiteur_format.setForeground(QtCore.Qt.red) delimiteur_motif = "[\)\(]+|[\{\}]+|[][]+" delimiteur_regex = QtCore.QRegExp(delimiteur_motif) self.regles.append([delimiteur_regex, delimiteur_format]) #-------------------------------------------------------------------- # commentaire sur une seule ligne et jusqu'à fin de ligne: --...\n comment_format = QtGui.QTextCharFormat() comment_format.setForeground(QtCore.Qt.gray) comment_motif = "--[^\n]*" comment_regex = QtCore.QRegExp(comment_motif) self.regles.append([comment_regex, comment_format]) #-------------------------------------------------------------------- # commentaires multi-lignes: /*...*/ self.commentml_format = QtGui.QTextCharFormat() self.commentml_format.setForeground(QtCore.Qt.gray) self.commentml_deb_regex = QtCore.QRegExp("/\\*") self.commentml_fin_regex = QtCore.QRegExp("\\*/") #======================================================================== def highlightBlock(self, text): """analyse chaque ligne et applique les règles""" # analyse des lignes avec les règles for expression, tformat in self.regles: index = expression.indexIn(text) while index >= 0: length = expression.matchedLength() self.setFormat(index, length, tformat) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) #pour les commentaires multilignes: /* ... */ startIndex = 0 if self.previousBlockState()!=1: startIndex = self.commentml_deb_regex.indexIn(text) while startIndex>=0: endIndex = self.commentml_fin_regex.indexIn(text, startIndex) if endIndex==-1: self.setCurrentBlockState(1) commentml_lg = len(text)-startIndex else: commentml_lg = endIndex-startIndex + \ self.commentml_fin_regex.matchedLength() self.setFormat(startIndex, commentml_lg, self.commentml_format) startIndex = self.commentml_deb_regex.indexIn(text, startIndex+commentml_lg)
#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division # Python 2.7 import sys, os from PyQt4 import QtCore, QtGui from bibcolorsyntaxsql import ColorSyntaxSQL # pour coloration syntaxique SQL ############################################################################# class Fenetre(QtGui.QWidget): def __init__(self, scriptsql, parent=None): super(Fenetre, self).__init__(parent) self.resize(800, 600) self.scriptsql = scriptsql self.edit = QtGui.QTextEdit(self) # met une police de caractère même largeur pour tous les caractères font = QtGui.QFont() font.setFamily(u"DejaVu Sans Mono") # police de Qt4 font.setStyleHint(QtGui.QFont.Courier) # si la police est indisponible font.setPointSize(10) self.edit.setFont(font) # met en place la coloration syntaxique self.colorSyntaxSQL = ColorSyntaxSQL(self.edit.document()) # affiche le script colorisé self.affiche(self.scriptsql) # positionne le QTextEdit dans la fenêtre posit = QtGui.QGridLayout() posit.addWidget(self.edit, 0, 0) self.setLayout(posit) #======================================================================== def affiche(self, chaineunicode): """Ecrire dans le widget edit (QTextEdit)""" self.edit.append(chaineunicode) # fait bouger le curseur à la fin du texte self.edit.moveCursor(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor) # force le rafraichissement pour affichage en temps réel QtCore.QCoreApplication.processEvents() ############################################################################# if __name__ == "__main__": app = QtGui.QApplication(sys.argv) # script SQL à afficher scriptsql = u""" -- ************************************************************************** -- controle des notifications /* toutes les photos par auteur avec acceptations et prix/trophée "photos" script mis à jour 2/2014 */ Select distinct photos.bord as bord, photos.section as section, photos.numphot as numphot, photos.photo as photo, photos.titre as titre, (CASE WHEN photos.accept='*' THEN 'Accepté' ELSE 'Refusé' END) AS acceptation, (CASE WHEN photos.photo in (select prixphotos.photo from prixphotos) THEN (select prixphotos.prix from prixphotos where prixphotos.photo=photos.photo) ELSE '' END) AS prix, (CASE WHEN photos.photo in (select prixphotos.photo from prixphotos) THEN (select prixphotos.trophee from prixphotos where prixphotos.photo=photos.photo) ELSE '' END) AS trophee from photos, prixphotos group by photos.photo order by photos.bord, photos.section, photos.numphot """ fen = Fenetre(scriptsql) fen.show() sys.exit(app.exec_())
Et voilà comment ce sera affiché:
Bien entendu, rien ne vous empêche d'appliquer cela sur n'importe quel langage! En fonction de la complexité de la syntaxe, ce ne sera pas forcément simple, mais ce sera possible.
Amusez-vous bien!