Le repérage des paires de parenthèses avec QSyntaxHighlighter

| Geir Vattekar | traducteur : Louis du Verdier | Qt Quarterly |

Une fonctionnalité de QSyntaxHighlighter très souvent oubliée ou négligée est sa faculté à lier les données utilisateur avec le bloc de texte en cours de coloration. Parmi les nombreuses possibilités qu'il offre pour l'édition de texte avancée, nous avons choisi le repérage des parenthèses - qu'un éditeur qui se respecte ne peut ignorer.

Bien que le but principal de cet article soit d'éclaircir certaines capacités obscures de QSyntaxHighlighter et de leur trouver une utilité, il montrera également la manière de les utiliser dans le but d'ajouter des sélections supplémentaires dans un QPlainTextEdit.

Cet article est une traduction autorisée de Matching Parentheses with QSyntaxHighlighter de Geir Vattekar.

Commentez !

- Titre : Le repérage des paires de parenthèses avec QSyntaxHighlighter

- Auteurs : Geir Vattekar, traducteur : Louis du Verdier, Qt Quarterly

- Parution : 1 novembre 2010

- Mise à jour : 14 novembre 2010

- Licence : Copyright ® 2009 David Boddie. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Vue d'ensemble

- III. Le colorateur syntaxique

- IV. L'éditeur

- V. Au-delà des paires de parenthèses

- XI. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Qt à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Matching Parentheses with QSyntaxHighlighter de Geir Vattekar paru dans la Qt Quarterly Issue 31.

Cet article est une traduction de l'un des tutoriels en anglais écrits par Nokia Corporation and/or its subsidiary(-ies) et inclus dans la documentation de Qt. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Vue d'ensemble

Pour implémenter le repérage des parenthèses, quelques classes sont nécessaires. Tout d'abord, nous avons besoin d'une sous-classe de QSyntaxHighlighter. Cette sous-classe enregistrera la position de chaque parenthèse et stockera cette information dans le bloc de texte. Puis nous héritons QPlainTextEdit pour créer un éditeur qui puisse à la fois parser les informations des parenthèses depuis les blocs de texte et colorer ces parenthèses en vert. Nous avons également besoin de deux classes "d'aide" pour l'éditeur de texte et pour le colorateur syntaxique.

Avant de se lancer dans la visite du code, commençons par regarder rapidement l'architecture sous-jacente de Qt. Un QTextDocument comporte une série de QTextBlock, les conteneurs de fragments de texte du document. Qt vous permet de stocker des données personnalisées dans chaque bloc en sous-classant QTextBlockUserData pour utiliser la sous-classe avec QTextBlock. Le document peut alors être affiché dans un widget tel que QTextEdit ou QPlainTextEdit, c'est-à-dire des widgets qui ont accès aux blocs de texte et aux données utilisateur.

QSyntaxHighlighter obtient un accès aux blocs de texte visibles avant qu'ils soient affichés et peut par conséquent stocker les données utilisateur. Après cette courte description du système de gestion du texte de Qt, nous sommes prêts à regarder de plus près les classes nécessaires à cette expérience.

III - Le colorateur syntaxique

Pour cet article, nous avons implémenté la classe Highlighter, qui hérite de QSyntaxHighlighter. Commençons par étudier sa définition :

class Highlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
Highlighter(QTextDocument *document);
protected:
void highlightBlock(const QString &text);
};

La fonction highlightBlock() est appelée avec le texte du bloc qui devrait être coloré. L'accès au bloc lui-même est donné par des fonctions de QSyntaxHighlighter. Par exemple, setFormat() définit le QTextCharFormat des morceaux du bloc ; setCurrentBlockUserData(), les données utilisateur sur le bloc de texte. Cette dernière fonction va nous aider ici.

Nous introduisons deux nouveaux types pour supporter le colorateur syntaxique et l'éditeur de texte : TextBlockData, une classe qui hérite de QTextBlockUserData, et ParenthesisInfo, une structure qui garde une trace de la position et de la direction d'une parenthèse. Voici leur définition :

struct ParenthesisInfo
{
char character;
int position;
};
class TextBlockData : public QTextBlockUserData
{
public:
TextBlockData();
QVector<ParenthesisInfo *> parentheses();
void insert(ParenthesisInfo *info);
private:
QVector<ParenthesisInfo *> m_parentheses;
};

Nous n'allons pas étudier l'implémentation de TextBlockData. La seule chose importante à savoir est que la liste d'informations de parenthèses est classée par position dans le bloc de texte.

Regardons comment Highlighter collecte et stocke les informations des parenthèses. Ce stockage est effectué dans highlightBlock() :

void Highlighter::highlightBlock(const QString &text)
{
TextBlockData *data = new TextBlockData;
int leftPos = text.indexOf('(');
while (leftPos != -1) {
ParenthesisInfo *info = new ParenthesisInfo;
info->character = '(';
info->position = leftPos;
data->insert(info);
leftPos = text.indexOf('(', leftPos + 1);
}
...
setCurrentBlockUserData(data);
}

Tout d'abord, nous créons une instance de TextBlockData, que nous remplissons avec les informations des parenthèses et que nous donnons au bloc de texte que nous colorons. Pour trouver les parenthèses dans le bloc, nous employons simplement une recherche linéaire dans text. Pour chaque parenthèse que nous trouvons, nous construisons un ParenthesisInfo que nous ajoutons dans nos données utilisateur.

Cette approche est quelque peu inadéquate pour une application réelle. Par exemple, elle ne capture pas les parenthèses présentes dans les chaines littérales mais, pour cet exemple, elle est suffisante.

IV - L'éditeur

Nous sous-classons un QPlainTextEdit pour créer un éditeur pouvant colorer les paires de parenthèses basées sur les données utilisateur en provenance du colorateur syntaxique. Étant donné que nous parlons d'édition de code, nous préférons utiliser QPlainTextEdit plutôt que QTextEdit puisque cette classe est optimisée pour la gestion de texte simple ; cependant, nous pourrions choisir de sous-classer QTextEdit à la place sans avoir à changer la moindre ligne de code. Aussi, dans un QPlainTextEdit, chaque ligne représente un QTextBlock. Bien que cel n'ait pas d'importance dans le cas présent, cela rend d'autres fonctionnalités plus simples à implémenter, par exemple les numérotations de lignes.

Si le curseur est sur une parenthèse, le moment est venu de rechercher sa parenthèse associée - ou de s'arrêter si nous ne pouvons pas la trouver. Cette vérification est faite à chaque fois que la position du curseur change. Nous avons de la chance, QPlainTextEdit fournit le signal cursorPositionChanged(), que nous connectons à matchParentheses().

void TextEdit::matchParentheses()
{
QList<QTextEdit::ExtraSelection> selections;
setExtraSelections(selections);
TextBlockData *data =
static_cast<TextBlockData *>(
textCursor().block().userData());

Les paires de parenthèses sont colorées avec une sélection dite supplémentaire, donc quand la position du curseur change, les sélections précédentes doivent être retirées. Les données utilisateur de Highlighter peuvent être parsées depuis le bloc de texte contenant le curseur.

if (data) {
QVector<ParenthesisInfo *> infos =
data->parentheses();
int pos = textCursor().block().position();
for (int i = 0; i < infos.size(); ++i) {
ParenthesisInfo *info = infos.at(i);
int curPos = textCursor().position() -
textCursor().block().position();
if (info->position == curPos - 1 &&
info->character == '(') {
matchLeftParenthesis(
textCursor().block(), i + 1, 0);
return;
}
if (info->position == curPos - 1 &&
info->character == ')') {
matchRightParenthesis(
textCursor().block(), i - 1, 0);
}
}
} ...

Pour chaque parenthèse présente dans le bloc de texte, nous vérifions si le curseur est juste après. Si c'est le cas, il est temps de chercher une paire, ce qui est fait dans mathLeftParenthesis() ou matchRightParenthesis(), en fonction du type de parenthèse que nous recherchons. Puisque les espaces forment un problème, cette recherche est exclue de cet article mais l'idée est de faire simplement une recherche linéaire à travers les parenthèses restantes du document. Vous pouvez récupérer le code depuis la page Web de Qt Quarterly si vous souhaitez regarder cela de plus près.

Si une paire de parenthèses est trouvée, nous la colorons avec une sélection supplémentaire. De telles sélections forment une fonctionnalité de QPlainTextEdit qui permet d'ajouter des sélections par programmation, similaires à celles que l'utilisateur crée avec sa souris ou son clavier.

void TextEdit::createParenthesisSelection(int pos)
{
QList<QTextEdit::ExtraSelection> selections;
QTextEdit::ExtraSelection selection;
selection.format.setBackground(Qt::green);
QTextCursor cursor = textCursor();
cursor.setPosition(pos);
cursor.movePosition(QTextCursor::NextCharacter,
QTextCursor::KeepAnchor);
selection.cursor = cursor;
selections.append(selection);
setExtraSelections(selections);
}

V - Au-delà des paires de parenthèses

Nous avons vu comment QSyntaxHighlighter peut être utilisé à d'autres fins qu'une simple coloration syntaxique. Plus spécialement, nous avons exploré sa faculté à lier les données utilisateur aux blocs de texte qu'il colore. Si nous nous aventurons au-delà du repérage des paires de parenthèses, nous pouvons découvrir d'autres utilités de cette capacité.

Par habitude, nous souhaiterons aider le moteur de rendu du document textuel avec de l'information au vol sur les blocs de texte individuels. Par exemple, un éditeur qui effectue une vérification orthographique peut vouloir souligner les fautes de frappe avec des lignes rouges ondulées.

XI - Divers

Merci à johnlamericain pour ses conseils et ses encouragements durant la traduction ! Merci également à Buffering, dourouc05 et eusebe19 pour leur relecture attentive !

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisés à traduire cet article !