Introduction

Dans ce billet, nous allons créer pas à pas un éditeur de texte avec coloration syntaxique pour le langage de script lua (cf. LUA).

Objectifs

L'éditeur sera simple, en ce sens qu'il ne fera que de la coloration syntaxique. Nous prendrons en compte les éléments suivants :

  1. mots clés du langage (cf. Manuel LUA)
  2. commentaires
  3. autres éléments

Les deux premiers éléments recevront une mise en forme particulière.

Pré-requis

  • Les notions de plugin Eclipse et d'extension (dans la terminologie Eclipse RCP) sont requises pour comprendre la mise en oeuvre de l'éditeur.
  • L'environnement utilisé est Eclipse 3.3, les classes utilisées peuvent différer dans les versions précédentes.

Création de l'éditeur

Création d'un nouveau plugin

Nous allons commencer par créer un nouveau plugin vide. Pour cela, on utilise le menu Fichier>Nouveau...>Projet.... A l'ouverture de la boite de dialogue, il suffit de choisir l'entrée Projet de plugin puis choisir suivant. On renseigne le nom (damien.tutorial.eclipse.editor.lua), et on indique que le projet est conçu pour Eclipse 3.3, puis on choisi suivant. La deuxième page de la boite de dialogue permet de choisir certains réglages du plugin, on ne sélectionnera pas Générer une classe d'activation..., par contre on cochera Ce plugin apporte des contributions à l'interface. La rubrique Voulez vous créer une application RCP sera renseignée à non. Une fois ces réglages choisis, on clique sur Terminer et notre nouveau projet de plugin est prêt.

1 - nom du projet et emplacement 2 - options de création

L'environnement de développement a automatiquement ouvert un éditeur des propriétés du plugin. Dans le cas qui nous intéresse, nous allons utiliser simplement deux onglets : Dépendances et Extensions

Ajout des dépendances

La première étape va être de s'assurer que le plugin liste bien les dépendances nécessaires :

  • org.eclipse.core.runtime
  • org.eclipse.ui
  • org.eclipse.ui.editors
  • org.eclipse.jface.text

Pour ajouter une dépendance, il suffit de cliquer sur le bouton Ajouter... puis de sélectionner la ou les dépendance(s) voulue(s). La zone d'édition en haut de la boite de dialogue permet de filtrer la liste des plugins (celle-ci peut être plutôt longue en fonction de ce qui est installé).

3 - les dépendances

Codage de l'éditeur

La classe LuaEditorScanner

Cette classe va nous permettre de décomposer un fichier lua afin d'en extraire des éléments que nous classerons en trois catégories :

  • mots-clés
  • commentaires
  • le reste

A chaque catégorie, on va associer une mise en forme particulière, ce qui au final permettra d'obtenir la coloration syntaxique désirée.

LuaEditorScanner dérive de la classe RuleBasedScanner et est configurée en créant des règles servant à déterminer la catégorie d'un élément. On crée trois Token pour représenter chaque catégorie :

Token keyword = new Token(new TextAttribute(COLOR_KEYWORD, null, SWT.BOLD));
Token comment = new Token(new TextAttribute(COLOR_COMMENT, null, SWT.ITALIC));
Token other = new Token(new TextAttribute(COLOR_OTHER));

Afin de détecter les mots clés, on crée une règle de type WordRule. Chaque mot clé est ajouté via la méthode addWord qui prend en argument le mot clé et le token correspondant, on pourrait de cette manière définir plusieurs catégories de mots clés simplement en utilisant cette classe. Le constructeur de WordRule permet d'indiquer :

  • la catégorie par défaut si l'élément ne correspond pas à un mot de la liste spécifiée, on va indiquer other
  • une classe permettant de déterminer si un caractère correspond à un début d'identifiant ou à un contenu d'identifiant, ici on utilise la classe Character et on se base sur les méthodes de reconnaissance d'identifiant java
  • si la règle est sensible à la casse ou non, par défaut elle l'est
WordRule rule = new WordRule(new IWordDetector() {
	@Override
	public boolean isWordPart(char c) {
		return Character.isJavaIdentifierPart(c);
	}
	@Override
	public boolean isWordStart(char c) {
		return Character.isJavaIdentifierStart(c);
	}			
}, other);

Il nous reste à configurer l'ensemble des règles utilisables par notre scanner. Cela se fait par la méthode setRules qui prend en paramètre un tableau contenant les règles à employer. On y crée la règle permettant d'identifier les commentaires, à cette fin on crée une instance de la classe SingleLineRule spécialisée dans la détection d'éléments sur une ligne :

new SingleLineRule("--", null, comment, (char) 0, true)

On indique, le délimiteur marquant le début de l'élément (ici : --), le délimiteur marquant la fin de l'élément, s'il n'y en a pas, on indique null, la catégorie de l'élément (comment), le caractère d'échappement, et un booléen indiquant si la fin du fichier termine une ligne.

Si on place tout ceci ensemble on obtient le code suivant :

package damien.tutorial.eclipse.editor.lua;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WordRule;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;

/**
 * Défini le scanner de source lua.
 * @author damien
 */
public class LuaEditorScanner extends RuleBasedScanner {
	private static final Color COLOR_KEYWORD;
	private static final Color COLOR_COMMENT;
	private static final Color COLOR_OTHER;
	private static final String [] KEYWORDS = new String {
		"and", "break", "do", "else", "elseif",
	     "end", "false", "for", "function", "if",
	     "in", "local", "nil", "not", "or",
	     "repeat", "return", "then", "true", "until", "while"		
	};
	
	static {
		Display d = PlatformUI.getWorkbench().getDisplay();
		COLOR_KEYWORD = new Color(d, 0, 0, 128);
		COLOR_COMMENT = new Color(d, 0, 128, 0);
		COLOR_OTHER = new Color(d, 0, 0, 0);
	}
	
	/**
	 * Initialise le scanner.
	 */
	public LuaEditorScanner() {
		Token keyword = new Token(new TextAttribute(COLOR_KEYWORD, null, SWT.BOLD));
		Token comment = new Token(new TextAttribute(COLOR_COMMENT, null, SWT.ITALIC));
		Token other = new Token(new TextAttribute(COLOR_OTHER));
		
		WordRule rule = new WordRule(new IWordDetector() {
			@Override
			public boolean isWordPart(char c) {
				return Character.isJavaIdentifierPart(c);
			}

			@Override
			public boolean isWordStart(char c) {
				return Character.isJavaIdentifierStart(c);
			}			
		}, other);
		
		for (String k : KEYWORDS) {
			rule.addWord(k, keyword);
		}
		
		setRules(new IRule {
				rule,
				new SingleLineRule("--", null, comment, (char) 0, true)
		});		
	}
}
La classe LuaEditorConfiguration

A présent que nous disposons d'un scanner de source lua, il nous faut le déclarer. On va pour cela créer une classe permettant de configurer l'éditeur de code source. La classe LuaEditorConfiguration va dériver de la classe TextSourceViewerConfiguration qui fournit un paramétrage par défaut pour configurer un éditeur de texte destiné à la manipulation de code source. Nous allons simplement redéfinir la méthode getRepresentationReconciler qui est appelée automatiquement et sert à fournir une classe permettant de mettre à jour la mise en forme du document au fur et à mesure de la saisie. Pour cela, on défini deux éléments clé : un damager et un repairer.

Le Damager (interface IPresentationDamager) est chargé de déterminer quelle portion du document doit être à nouveau mise en forme suite à un changement de celui-ci; Le Repairer (interface IPresentationRepairer) se charge de calculer les nouveaux styles à appliquer sur cette portion de document. Dans notre cas, on crée une instance de DefaultDamagerRepairer qui implémente les deux interfaces précédentes et utilise un scanner afin de déterminer les styles à appliquer. Nous allons simplement lui fournir une instance de notre LuaEditorScanner. On obtient alors la classe suivante :

package damien.tutorial.eclipse.editor.lua;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;

/**
 * Défini la configuration pour notre éditeur lua
 * @author damien
 */
public class LuaEditorConfiguration extends TextSourceViewerConfiguration {
	@Override
	public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
		PresentationReconciler pr = new PresentationReconciler();
		DefaultDamagerRepairer ddr = new DefaultDamagerRepairer(new LuaEditorScanner());
		pr.setDamager(ddr, IDocument.DEFAULT_CONTENT_TYPE);
		pr.setRepairer(ddr, IDocument.DEFAULT_CONTENT_TYPE);
		return pr;
	}
}
La classe LuaEditor

Il s'agit de la partie la plus simple de notre éditeur. Nous allons créer une nouvelle classe dérivant de TextEditor, cette classe se contentera "d'indiquer" sa configuration, c'est à dire de passer une nouvelle instance de LuaEditorConfiguration à la méthode setSourceViewerConfiguration. On obtient le code suivant :

package damien.tutorial.eclipse.editor.lua;

import org.eclipse.ui.editors.text.TextEditor;

/**
 * Défini notre éditeur pour les fichiers script lua.
 * @author damien
 */
public class LuaEditor extends TextEditor {
	/**
	 * Crée et configure notre éditeur.
	 */
	public LuaEditor() {
		setSourceViewerConfiguration(new LuaEditorConfiguration());
	}
}

Ajout de l'extension

A présent que les classes composant notre éditeur sont réalisées, il ne nous reste plus qu'à créer une extension permettant d'utiliser l'éditeur en question. C'est cette extension qui fait qu'une fois notre plugin chargé par eclipse, l'éditeur LuaEditor sera disponible pour l'utilisateur.

Pour configurer notre extension, il faut retourner dans la page de configuration de notre plugin (ouvrir plugin.xml). On choisi alors l'onglet Extensions. Une fois dans cette page, on ajoute une nouvelle extension avec le bouton Ajouter.... Il faut choisir une extension de type org.eclipse.ui.editors. Un clic droit sur cette extension permet de définir un nouvel éditeur. La zone droite de la page va permettre de renseigner les éléments qui vont permettre de configurer l'éditeur. Cinq champs vont permettre de configurer notre éditeur :

  • id : défini l'identifiant de notre éditeur, généralement on choisi le nom du plugin
  • name : défini le nom de l'éditeur tel qu'il apparaît dans la liste des éditeurs dans Ouvrir avec...
  • icon : associe une image (généralement 16x16) à notre éditeur, sans cette image l'éditeur n'apparaît pas dans la liste des éditeurs
  • extensions : associe l'éditeur à une ou plusieurs extensions de fichier, ici on va indiquer "lua"
  • class : indique la classe java correspondant au code de notre éditeur : damien.tutorial.eclipse.editor.lua.LuaEditor

4 - configuration de l'extension

Test de notre éditeur

5 - résultat de la mise en forme Afin de tester notre éditeur, revenons sur la page d'accueil de la configuration du plugin, puis cliquons sur Lancer une application Eclipse. Un nouvel environnement comprenant notre plugin est chargé. Il ne nous reste plus qu'à ouvrir ou créer un nouveau fichier lua et à l'éditer pour voir les mots clé et les commentaires se colorer au fur et à mesure de la saisie !

La vue supérieure montre notre éditeur en action, les mots clés apparaissent en bleu et les commentaires en vert. Le même code est présenté dessous dans un simple éditeur de texte.

Pour utiliser notre éditeur dans notre environnement de développement, il suffit de l'exporter (soit en archive soit dans un répertoire) et de copier le résultat dans le répertoire plugins d'eclipse. Au prochain démarrage d'eclipse, notre éditeur sera disponible.

Et maintenant ?

Notre éditeur est loin d'offrir toutes les fonctionnalités d'un outil de développement, ce n'est qu'un "simple" éditeur de texte colorant certains mots. Il sera intéressant d'y ajouter :

  • complétion automatique
  • validation du code source lors de sa sauvegarde, détection des erreurs de compilation
  • coloration syntaxique des chaînes de caractères, des commentaires multi-ligne
  • ...