Le fonctionnement de Valhalla, partie 3 : afficher les suggestions

Et voici donc le second article de cette série où je vous explique le fonctionnement du package Atom que je développe : Valhalla. Aujourd’hui, on va voir comment Valhalla fait pour afficher ses suggestions.

Tout ce passe dans dans le fichier provider.js (ou presque). On y trouve une classe (ValaProvider) divisée en trois grandes parties :

  • L’initialisation, où on va notamment lancer la découpe de notre code, comme on l’a vu dans la partie précédente. On va aussi préciser à Atom que le moteur de suggestions n’est valable que dans du Vala ;
  • Le moteur de suggestions, qui va dire quelle suggestions afficher en fonction du contexte, en se basant sur le code qu’on a découpé auparavant ;
  • Des fonctions pour nous aider un peu, par exemple pour afficher facilement une méthode dans les suggestions ou pour déterminer le type d’une expression.

On va surtout s’intéresser à la deuxième partie, c’est là que la plupart des choses se font.

Le moteur de suggestions

Pour obtenir ses suggestions, Atom va exécuter la fonction getSuggestions de notre classe (on verra plus tard comment il sait quelle fonction prendre). Cette fonction retourne une Promise qui est résolue avec un tableau contenant les suggestions sous forme d’objets, ressemblant généralement à ça.

{
    text: 'Suggestion',
    type: 'function',
    leftLabel: 'Super',
    rightLabel: 'Génial',
    description: 'Une super suggestion, vraiment géniale !',
    descriptionMoreURL: 'https://bat41.gitlab.io'
}

Ce code donnera quelque chose comme ça :

Notre suggestion

On a aussi d’autres options plus avancés, si ça vous intéresse, vous pouvez aller jeter un coup d’œuil au wiki du package qui gère l’autocomplétion.

On crée donc un tableau vide qui contiendra toutes nos suggestions par la suite, puis on utilise la méthode qu’on a déjà vu pour explorer notre arbre de blocs de code, et on filtre en fonction du contexte.

// Version (très simplifiée) pour que vous compreniez le principe.
getSuggestions ({editor, bufferPosition, scopeDescriptor, prefix, activatedManually}) {
    // On obtient la ligne actuelle.
    var line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);

    return new Promise ((resolve) => {
        let suggestions = [];

        const trimLine = line.trim ();

        // La méthode récursive utilisée pour explorer les blocs de code.
        let explore = (scope) => {

            // Ici par exemple, si la ligne commence par `using `,
            // on affiche les différents espaces de noms disponibles
            if (trimLine.startsWith('using ')) {
                // Le bloc actuel est un espace de nom
                if (scope.data && scope.data.type == 'namespace') {
                    // Il commence comme ce que l’utilisateur a tapé
                    if (name.startsWith(prefix) || prefix == ' ') {
                        // show suggestion
                        let suggestion = {
                            text: name + ';',
                            type: 'import',
                            displayText: name,
                            description: `The ${name} namespace.`
                        };
                        suggestions.push (suggestion);
                    }
                }
            }

            // On explore les enfants
            for (child of scope.children) {
                explore (child);
            }
        }

        // On explore tout les scopes à la racine
        for (scope of this.manager.scopes) {
            explore (scope);
        }

        // ON envoie nos suggestions
        if (trimLine != '') {
            resolve(suggestions);
        }
    });
}

En réalité, c’est bien plus complexe que ça, mais le principe reste toujours le même.

Lier tout ça au reste d’Atom

On a maintenant un joli moteur de suggestions, sauf qu’Atom ne sait pas qu’il faut l’utiliser ! Résultat : il ne se passe rien…

Il faut savoir qu’à peu près tout dans Atom est un package (et c’est peut être la plus grande force de cet éditeur) : la fonction rechercher, l’affichage des onglets, des suggestions… Et les différents packages ont un moyen très simple de communiquer entre eux, appelé services. Un package propose un service, les autres peuvent y souscrire et ils échangent des information ainsi. C’est par exemple ce que fait autocomplete-plus, le package qui affiche les suggestions : on peut ainsi lui donner nos propres moteurs de suggestions. Et Valhalla utilise ce service, grâce à ce morceau de son manifeste.

"providedServices": {
  "autocomplete.provider": {
    "versions": {
      "2.0.0": "getProvider"
    }
  }
}

Ici, on indique à autocomplete-plus qu’il peut obtenir notre moteur grâce à la méthode getProvider, qui se trouve dans l’objet exporté par Valhalla (export default ou module.export) , situé dans valhalla.js. Cette méthode retourne simplement une instance de la classe ValaProvider ! Et voilà, Atom sait où aller chercher les suggestions pour le code Vala et peut les afficher.


Cet article est terminé, mais un autre devrait bientôt arriver. En tout cas j’espère qu’il vous aura intéressé.