Catégorie : Programmation     Tags :      

    Retrouvez cet article dans : Linux Magazine 79

    Vim est un éditeur de texte dérivé de Vi. Ce dernier est disponible sur tous les systèmes Unix. Même si, au début, Vim n’était qu’une des copies de Vi, ce logiciel s’est démarqué par la suite par un lot de fonctionnalités supplémentaires. Cet article présente l’écriture d’un plugin. L’algorithme utilisé reste relativement simple. Il présente les principales fonctionnalités de Vim, mais aussi les limites du langage de script.

    Le Sudoku est un jeu récent ayant des règles très simples. Cependant, certaines grilles s’avèrent être de véritables casse-têtes. Les grilles sont constituées de 81 cases. Ce sont des carrés de 9x9. La grille est encore séparée en 9 carrés de 3x3.
    Chaque case doit contenir un chiffre de 1 à 9. Il faut alors qu’il n’y ait pas deux fois le même chiffre dans une ligne, dans une colonne ou dans une des 9 zones de 3x3. Le programme proposé dans ce magazine réalisera des grilles aléatoires. Vous pourrez ainsi jouer pendant des heures entières.
    Malheureusement pour vous, il va d’abord falloir travailler un peu. Vim dispose d’un langage très particulier. Non seulement l’apprentissage n’est pas forcément évident, mais en plus, il manque des fonctionnalités essentielles. Nous reviendrons sur ce point plus tard. Avant toute chose, il faut préparer l’algorithme. Tout d’abord, il faut créer une grille. Il s’agit d’un tableau à deux dimensions. Ce point pose déjà un sérieux problème : Vim ne gère pas les tableaux !
    Un nombre A entre 15 et 35 est choisi au hasard. Cela correspond au nombre de cases initialement remplies. Puis, A cases sont sélectionnées au hasard. Pour chacune d’elles, le chiffre est tiré au sort parmi les chiffres possibles. La grille est alors affichée. À chaque modification d’une valeur, la grille doit être mise à jour. Cette opération est très importante. Bien sûr, il est important de pouvoir distinguer une case remplie à la création d’une autre remplie par l’utilisateur. Nous pouvons alors nous apercevoir d’une nouvelle difficulté : Vim ne dispose d’aucune fonction pour tirer un nombre au hasard.
    Nous connaissons les grandes lignes de l’algorithme, il est temps de commencer à coder. Avant tout, il faut déclarer le script comme un plugin. Certaines informations obligatoires sont à indiquer en en-tête. Il s’agit notamment du nom de l’auteur, de la version et de la licence du fichier. Cela se réalise dans un bloc de commentaire. C’est-à-dire que chaque ligne commence par le caractère guillemet, conformément à la syntaxe du langage. Pour le jeu du Sudoku, le fichier commence donc ainsi.

    « Vim global plugin for Sudoku
    « Last Change: 2005 Nov 11
    “ Maintainer: Christophe Buffenoir
    “ License: GPL v2.0

    Il faudra bien sûr mettre le champ Last Change à jour pour chaque modification publiée. Le caractère guillemet sera utilisé en permanence pour le commentaire. Mais il peut également servir pour une chaîne de caractère. Par conséquent, il est vivement recommandé de ne pas utiliser de commentaire sur une ligne contenant du code. Le but est de garder un fichier facilement lisible par l’être humain. La syntaxe même du langage natif de Vim impose d’être méfiant vis à vis de la qualité du code.
    Il est temps de présenter le folding. Sous ce terme barbare se cache une fonctionnalité très intéressante. Le folding consiste à cacher une partie de code. Vim gère plusieurs modes de cette fonctionnalité. Le fichier de Sudoku fourni sur le site du magazine est prévu pour la méthode «markers», que l’on peut activer par la commande set foldmethod=marker. Le fichier est séparé en sections, sous-sections et fonctions.
    Chacune de ces entités a un marqueur de folding récursif (niveau 1 pour la section, 2 pour la sous-section et 3 pour la fonction). Pour ouvrir une partie de code masquée, il suffit de se placer sur la ligne correspondante et de taper la commande «zo» en mode normal. Pour fermer une partie de code, la commande à utiliser est «zc». Il est également possible de réaliser ces deux opérations récursivement avec, respectivement, les commandes «zO» et «zC».
    Revenons alors à notre programme de Sudoku. Avant d’exécuter quoi que ce soit, il est important de vérifier que le plugin n’est pas déjà chargé. Pour cela, il suffit de tester l’existence d’une variable globale que nous définissons juste après. Cela se réalise par les quelques lignes suivantes.

    if exists(«loaded_sudoku»)
        finish
    endif
    let loaded_sudoku = 1

    Les prochaines variables ont une syntaxe particulière. Leur nom sera de la forme x:LeNom. x représente une lettre particulière. Le tableau suivant récapitule les différentes lettres possibles à la place de x et la propriété de la variable correspondante.

    x    La variable est
    b    locale au buffer
    w    locale à la fenêtre
    g    globale
    l     locale à la fonction
    s    locale au script
    a    un argument de fonction
    v    globale et prédéfinie par Vim

    Il faut alors exécuter la fonction principale à la demande de l’utilisateur. La ligne suivante lancera donc le jeu lorsque la commande \su est entrée.

    nmap <Leader>su :call Sudoku()<CR>

    La première question que l’on peut se poser est : quelles commandes pouvons-nous utiliser ? En fait, nous pouvons appeler toutes les fonctions possibles qui commencent par «:» dans le mode normal. Dans le script, nous pouvons nous passer du «:». Il est même possible d’utiliser des abréviations, mais cela est-il raisonnable ? Le langage est suffisamment difficile comme cela. Il n’est absolument pas évident de relire un programme où les commandes sont écrites en deux lettres. Mais avant tout, nous allons définir la fonction principale. Le code ci-dessous ne correspond pas à la fonction complète. Vous verrez des bouts de codes dans cet article et non pas le programme entier. Si vous souhaitez voir l’ensemble, vous pouvez regarder le fichier sudoku.vim sur le site du magazine. Le programme est également disponible sur mon site web, à l’adresse http://www.buffenoir.org.

    let s:s=»Jeu du Sudoku !»
    
    « Function: Sudoku() {{{3
    « Fonction principale
    function Sudoku()
        if !buflisted(s:s)
            exe ‘sp ‘.escape(s:s,’ ‘)
            resize 25
        else
            new|exe ‘b’ bufnr(s:s)
        endif
        q!
    endfunction

    La première ligne en dehors de la fonction donne le titre de la fenêtre. Les deux lignes suivantes sont du commentaire. En effet, il est préférable d’obtenir un code propre et lisible. Vous remarquerez le tag «{{{3» à la fin de la première ligne de commentaire. Il indique tout simplement le début d’un folding de niveau 3. Ce dernier est fermé soit par le tag «3}}}» soit par l’ouverture d’un folding de niveau inférieur ou égal à 3. La fonction se définie ensuite par le mot clé function. L’ajout d’un point d’exclamation à la fin du mot clé, par exemple function!, redéfinit la fonction si cette dernière est déjà déclarée. Ce ne sera pas nécessaire ici. En effet, comme nous l’avons vu, le programme s’arrête si le plugin a été chargé. Ensuite, nous ouvrons un nouveau buffer s’il n’existe pas, et nous lui donnons une dimension de 25 lignes. Dans le cas contraire, le buffer existant est rappelé. Il sera fermé par la commande q! à la fin du programme. Eh oui, toutes les commandes commençant par «:» dans le mode normal sont permises.
    Nous avons besoin d’un tableau à deux dimensions. Pour cela, il faut créer des fonctions spécifiques. Il n’existe malheureusement pas de tableaux natifs. Cependant, nous pouvons utiliser les chaînes de caractères. Pour cela, nous définissons deux fonctions : setCase et getCase. Il faut néanmoins définir le tableau de la grille. Pour cela, nous utiliserons une fonction spécifique.

    « Function: s:CreerGrille() {{{3
    « Création d’une grille de jeu
    function s:CreerGrille()
        « Initialisation de la grille
        let l:i = 0
        let b:grillePrincipale=’D’
        while l:i < 81
            let b:grillePrincipale = b:grillePrincipale.’0’
            let l:i=l:i + 1
        endwhile
    endfunction

    Il n’existe pas de boucle for dans le langage, que ce soit dans le sens shell ou C. Par conséquent, nous utilisons une itération while. La grille principale est donc une chaîne de 82 caractères dont le premier est la lettre D et les suivants des 0. Le formalisme utilisé ici se veut le plus simple possible.
    Une case contient le chiffre défini dans la grille, soit de 1 à 9. Si aucune valeur n’a été définie, le tableau contient un 0. Alors, pourquoi mettre un D au début ? Cela vient du fait que les variables dans Vim ne sont pas typées. Il est plus simple d’avoir une lettre au début de la chaîne pour être sûr du type en l’affichant durant les phases de débogage.
    Le mot clé let permet de définir ou de redéfinir une variable. Les chaînes de caractères s’écrivent indifféremment ‘comme ceci’ ou «comme cela». Il ne faudra pas oublier d’appeler la fonction CreerGrille dans la fonction Sudoku comme ceci.

    call s:CreerGrille()

    Le code suivant correspond aux fonctions setCase et getCase.

     « Function: s:setCase(x, y, n) {{{3
    “ Modifie une case de la grille
    function s:setCase(x, y, n)
        let l:posCase = (a:y*9)+a:x
        let l:longGrille = strlen(b:grillePrincipale)
        let l:grille = strpart(b:grillePrincipale, 0, l:posCase)
        let l:grille = l:grille.a:n
        let l:grille = l:grille.strpart(b:grillePrincipale, l:posCase+1, l:longGrille - l:posCase)
        let b:grillePrincipale = l:grille
    endfunction
    « Function: s:getCase(x, y) {{{3
    “ Récupère la valeur d’une case
    function s:getCase(x, y)
        let l:resultat = strpart(b:grillePrincipale, (a:y*9)+a:x, 1)
        return l:resultat
    endfunction

    La première remarque importante à faire correspond aux arguments d’une fonction. Les variables ne comportent pas la première lettre dans leur déclaration. Cependant, il faut rajouter a: avant le nom de la variable lors de l’utilisation. Deux fonctions sont ici utilisées : strlen et strpart. La première retourne la longueur d’une chaîne et la seconde, un extrait de chaîne. Cette dernière prend trois arguments : la chaîne de caractère, la position de début et la position de fin. L’opération . correspond à la concaténation de deux chaînes. Pour récapituler, nous avons une gestion de la grille physique et rien d’autre. Il faut alors remplir la grille avec des nombres et des cases tirés au sort. Or, il n’y a pas de fonction définie pour cela dans Vim. Nous utiliserons donc la variable RANDOM du système. Cela rend le programme incompatible avec la plate-forme Windows. Le code ci-dessous remplit la grille.

    let l:nbCasesR = system(«echo -n $RANDOM») % 26 + 15
    let l:i = 0
    while l:i < l:nbCasesR
        let l:x = system(«echo -n $RANDOM») % 9 + 1
        let l:y = system(«echo -n $RANDOM») % 9 + 1
        while s:getEtat(l:x, l:y) != 0
            let l:x = system(«echo -n $RANDOM») % 9 + 1
            let l:y = system(«echo -n $RANDOM») % 9 + 1
        endwhile
        let l:test = 1
        let l:j = 0
        while l:test == 1 && l:j < 3
            let l:valeur = system(«echo -n $RANDOM») % 9 + 1
            call s:MajCase(l:x, l:y, l:valeur, 1)
            if s:ChercheConflits(l:x, l:y, 0, 1) > 0
                call s:MajCase(l:x, l:y, 0, 0)
                if l:j == 2
                    let l:i = l:i - 1
                endif
            else
                let l:test = 0
            endif
            let l:j = l:j + 1
        endwhile
        let l:i = l:i + 1
    endwhile

    La première ligne permet d’obtenir un nombre de cases à remplir. Seulement, la variable RANDOM donne une valeur aléatoire entre 0 et 32767. Or, nous souhaitons avoir entre 15 et 35 cases pré-remplies. Pour cela, la valeur retenue est ensuite divisée par 26. Nous conservons alors le reste pour l’additionner à 15. Ensuite, les cases à compléter sont choisies au hasard (avec une valeur aléatoire pour x et pour y). Si la case est déjà complétée, une autre case est choisie. Une fois fait, une valeur est choisie et la case est mise à jour via une fonction MajCase que nous n’étudierons pas dans cet article. Cette fonction prend les mêmes paramètres que setCase. Elle a cependant un argument supplémentaire. Il s’agit d’un entier ayant pour valeur 0 pour supprimer la case, 1 pour une définition par le script et 2 pour une modification de l’utilisateur. Cela permet de choisir la couleur de l’affichage. La fonction ChercheConflits permet de savoir si la case nouvellement mise à jour n’engendre pas un conflit avec d’autres cases. Si tel est le cas, deux nouvelles tentatives pour trouver un chiffre correct sont effectuées. Si elles sont infructueuses, une autre case est choisie. La fonction d’affichage ne présente rien de bien complexe, elle ne sera donc pas décrite. Cependant, la méthode de colorisation est importante. Vim cherche des expressions régulières pour ajouter de la couleur. Pour cela, il suffit de mettre dans la fonction principale ces quelques lignes.

    hi Red term=reverse ctermfg=LightRed guifg=LightRed
    syn match Red «([123456789])»
    hi Green term=reverse ctermfg=LightGreen guifg=LightGreen
    syn match Green «`[123456789]`»
    hi IRed term=reverse ctermbg=Yellow ctermfg=LightRed guibg=Yellow guifg=LightRed
    syn match IRed «\[[123456789]\]»
    hi IGreen term=reverse ctermfg=DarkGreen ctermbg=White guibg=White guibg=DarkGreen
    syn match IGreen «,[123456789],»
    hi Sel term=reverse ctermfg=White ctermbg=Blue guibg=Blue guifg=White
    syn match Sel «\’[\.123456789]\’»

    Les commandes hi définissent des groupes de colorisation. Par exemple, la première ligne crée un groupe nommé «Red». Les paramètres suivants sont utilisés pour appliquer les couleurs. Un paramètre commençant par cterm s’occupe de Vim en console. Par contre, si le paramètre débute par gui, il est utilisé par gvim, un frontend graphique. Comme vous pouvez le deviner, fg et bg signifient respectivement foreground et background. Le premier donne la couleur du texte et le second celle du fond. La commande syn match, quant à elle, fait la correspondance entre un groupe de colorisation et l’expression régulière. Cela veut dire que nous devrons mettre des caractères de contrôle pour changer les couleurs des cases. Ainsi, une case en conflit sera bordée de parenthèses. Si elle est sélectionnée, les parenthèses seront remplacées par des crochets. Plusieurs fonctions permettent de passer une case à l’état sélectionné ou encore de passer d’une case en conflit à une case sans conflit. Par exemple, la fonction suivante passe la case dans un état en conflit.

    « Function: s:afficheImossible(x, y) {{{3
    « Affiche un chiffre comme possible
    function s:afficheImpossible(x, y)
        if s:getEtat(a:x, a:y) == 2
            let l:tempX = b:curseurX
            let l:tempY = b:curseurY
            call s:placeCurseur(a:x, a:y)
            exe ‘norm!hr(‘
            exe ‘norm!2lr)’
            exe ‘norm!h’
            call s:placeCurseur(l:tempX, l:tempY)
        endif
    endfunction

    Nous voyons alors l’appel à une fonction getEtat. Les valeurs fournies par cette fonction correspondent aux états vus plus haut (0 pour une case vide, 1 pour une case définie par le script et 2 pour une case modifiée par l’utilisateur). Les conflits ne sont affichés que pour les cases définies par l’utilisateur. La commande exe est particulière. Elle permet d’exécuter une autre commande. La chaîne de caractères passée en paramètre ici commence par «norm!». Cela signifie que Vim passe en mode normal avant d’exécuter la requête. Les commandes h et l provoquent respectivement un décalage du curseur d’un caractère sur la gauche et sur la droite. La commande r, quant à elle, remplace le caractère courant par celui qui est donné juste après. Ainsi «r(« remplace le caractère par «(«.
    La dernière fonction que nous verrons dans cet article correspond à la saisie de l’utilisateur. Le plugin est interactif. Par conséquent, il faut gérer les divers événements clavier

    function s:Saisie()
        let l:x = 0
        let l:y = 0
        let l:c=’t’
        while l:c != ‘q’
            let l:etat = s:getEtat(l:x, l:y)
            if l:c =~ ‘[hjkl]’
                if l:c == ‘k’
                    if l:y > 0
                        let l:y = l:y - 1
                    endif
                elseif l:c == ‘j’
                    if l:y < 8
                        let l:y = l:y + 1
                    endif
                elseif l:c == ‘h’
                    if l:x > 0
                        let l:x = l:x - 1
                    endif
                elseif l:c == ‘l’
                    if l:x < 8
                        let l:x = l:x + 1
                    endif
                endif
                call s:placeCurseur(l:x, l:y)
            elseif l:c =~’[123456789]’
                if l:etat == 0 || l:etat == 2
                    call s:MajCase(l:x, l:y, l:c, 2)
                endif
            elseif l:c =~ ‘[0.]’
                if l:etat == 2
                    call s:MajCase(l:x, l:y, 0, 0)
                endif
            endif
            call s:Selectionne(l:x, l:y)
            redraw
            let l:c=getchar()
            let l:c=nr2char(l:c)
        endwhile
    endfunction

    La commande pour récupérer une saisie utilisateur est située à la fin. Il s’agit de getchar. Vim attend l’appui sur une touche lorsqu’il exécute cette commande. Seulement, ce n’est pas le caractère qui est retourné mais un code. Il faut alors convertir ce dernier avec nr2char.
    Une série de test permet d’exécuter les actions nécessaires suite à l’appui de la touche. Lorsque les actions ont été exécutées, l’affichage est rafraîchi via la commande redraw. Avant de conclure, il reste un élément important. Les tests de cette fonction utilisent des expressions régulières.
    Pour cela, il existe deux opérateurs sous Vim. «=~» teste si la chaîne de caractères correspond à l’expression régulière qui suit. L’opération retourne vrai si cela est le cas.
    L’opérateur «!~» réalise exactement l’inverse : il retourne faux si la chaîne ne correspond pas. Vous trouverez plus d’informations concernant les commandes et la syntaxe de Vim dans la documentation officielle.
    Celle-ci est disponible directement sur le site web http://www.vim.org ou encore au sein de l’éditeur. Dans le dernier cas, vous pourrez afficher la page souhaitée avec la commande :help commande.
    Vim et Emacs sont deux éditeurs de texte plébiscités sous Linux. Même si aucun des deux ne prépare le thé ou le café, ils sont tous les deux extensibles à souhait. En plus, ils sont les sujets de nombreux trolls. L’étude de Vim a soulevé de nombreux problèmes dans le langage de script.
    Il est impossible de créer des plugins évolués de manière simple avec le langage natif. Heureusement, il est possible d’utiliser Perl, Python ou même Ruby !
    Seulement, avant de commencer l’écriture d’une extension, il est fortement recommandé de vérifier qu’aucun plugin correspondant n’existe sur le site officiel.

    Retrouvez cet article dans : Linux Magazine 79

    Posté par admin-web (fabrice) | Signature : Christophe Buffenoir | Article paru dans

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.


    • Il y a actuellement

    • 465 articles/billets en ligne.