Complétion personnalisée dans Gedit

Comment personnaliser la liste de suggestion de complétion dans l'éditeur de texte Gedit (ou tout autre éditeur de texte écrit en Gtk).

Présentation

Je rédige les contenu de ce site dans Gedit. Chaque contenu (note, article, ...) est un simple fichier texte formaté de manière à ce que je puisse en extraire certaines informations. Parmi celles-ci, une série de mots-clés (tags). La liste complète des tags est stockée dans un fichier json auquel je dois souvent me référer pour savoir quel mot-clé je peux utiliser ou lequel je dois ajouter. Cela peut être fastidieux, d'où l'idée d'écrire un petit programme python qui fournisse une liste de complétion quand je dois choisir ces mots-clés.

Complétion dans une vue texte

L'objet GtkSourceView est une vue texte qui permet la coloration syntaxique. Elle possède un objet de complétion (GtkSourceCompletion) auquel on peut ajouter des fournisseurs de complétion (GtkSourceCompletionProvider) qui seront utilisés lorsque l'utilisateur demande à compléter un mot ou une expression.

Lorsqu'une complétion est demandée, un contexte de complétion (GtkSourceCompletionContext) est créé ; les fournisseurs sont alors appelés, d'abord pour savoir s'ils doivent être utilisés dans ce contexte (méthode do_match) puis, si c'est le cas, pour alimenter le contexte de complétion (méthode do_populate) avec des propositions de complétions (GtkSourceCompletionProposal).

Le plugin Gedit « Word Completion » présent dans le package gedit-plugins est un exemple d'utilisation de GtkSourceCompletion : il fournit une liste de mots déjà écrits dans le document courant ou dans les documents ouverts. Il utilise pour cela le fournisseur dédié GtkSourceCompletionWords et un objet de proposition par défaut, GtkSourceCompletionItem.

Fournisseur de complétion personnalisé

Voici par exemple une implémentation minimale d'un fournisseur de complétion personnalisé :

# -*- coding: utf-8 -*-

from gi.repository import GObject
from gi.repository import GtkSource

class CustomProvider(GObject.GObject, GtkSource.CompletionProvider):
    
    def do_get_name(self):
        '''
        Doit retourner le nom du fournisseur.
        @return {String}
        '''
        return 'Custom provider'
    
    def do_match(self, context):
        '''
        Méthode interrogée pour savoir si le fournisseur de complétion doit
        être appelé pour alimenter le contexte.
        @param {GtkSourceCompletionContext} context
        @return {Boolean}
        '''
        return True
    
    def do_populate(self, context):
        '''
        Méthode appelée lorsque la liste de complétion doit être alimentée.
        @param {GtkSourceCompletionContext} context
        '''
        context.add_proposals(self, [
            GtkSource.CompletionItem(label='foo', text='foo'),
            GtkSource.CompletionItem(label='bar', text='bar'),
        ], True)
    
    def get_prefix(self, piter):
        '''
        Récupérer le mot tapé avant la demande de complétion.
        @param {GtkTextIter} piter
        @return {String} Le mot tapé ou None.
        '''
        start_iter = piter.copy()
        if start_iter.backward_word_start():
            return start_iter.get_visible_text(piter)
        return None
Squelette de fournisseur de complétion personnalisé.

La méthode do_match retourne toujours True ; le fournisseur sera donc appelé à chaque fois qu'une complétion est demandée. C'est à ce niveau que l'on peut filtrer son appel en testant par exemple la nature du texte courant. La méthode do_populate fournit une liste de 2 propositions totalement indépendantes du contexte. On peut construire cette liste en évaluant ce qui a été tapé par l'utilisateur via la méthode get_prefix écrite ici à titre d'exemple. On utilise enfin l'objet de proprosition par défaut mais il est aussi possible d'en créer un personnalisé, les objets GtkSourceCompletionProposal étant principalement caractérisés par 2 propriétés : « label » qui correspond au contenu affiché dans la liste de suggestion et « text » qui représente ce qui sera inséré dans le document si l'utilisateur sélectionne la proposition.

Une fois le fournisseur défini, on peut l'utiliser dans un objet GtkSourceView :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

from gi.repository import Gtk
from gi.repository import GtkSource

from .customcompletion import CustomProvider

class MyApplication(Gtk.Application):
    
    def do_activate(self):
        # GtkWindow
        window = Gtk.Window(
            application=self,
            title='Custom GtkSourceCompletionProvider',
            default_width=300,
            default_height=200
        )
        
        # GtkScrolledWindow
        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_border_width(5)
        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
                Gtk.PolicyType.AUTOMATIC)
        
        # GtkSourceView
        src_view = GtkSource.View(buffer=GtkSource.Buffer())
        
        # custom GtkSourceCompletionProvider
        provider = CustomProvider()
        src_view.get_completion().add_provider(provider)
        
        scrolled_window.add(src_view)
        window.add(scrolled_window)
        window.show_all()

# running the program
if __name__ == "__main__":
    app = MyApplication()
    exit_status = app.run(sys.argv)
    sys.exit(exit_status)
Exemple d'utilisation.

Fournisseur de complétion dans Gedit

Les fichiers ouverts dans Gedit sont affichés dans une vue GtkSourceView. Il est donc assez simple d'écrire un plugin qui permettrait d'utiliser le fournisseur de complétion personnalisé décrit plus haut :

# -*- coding: utf-8 -*-

from gi.repository import Gedit
from gi.repository import GObject

from .customcompletion import CustomProvider

class CustomCompletion(GObject.Object, Gedit.WindowActivatable):
    
    __gtype_name__ = "CustomCompletion"
    
    window = GObject.property(type=Gedit.Window)
    
    def __init__(self):
        GObject.Object.__init__(self)
    
    def do_activate(self):
        '''Activation du plugin.'''
        self.provider = CustomProvider()
        for view in self.window.get_views():
            self.add_provider(view)
        self.tab_added_id = self.window.connect('tab-added', self.on_tab_added)
        self.tab_removed_id = self.window.connect('tab-removed', self.on_tab_removed)
    
    def do_deactivate(self):
        '''Désactivation du plugin.'''
        for view in self.window.get_views():
            self.remove_provider(view)
        self.window.disconnect(self.tab_added_id)
        self.window.disconnect(self.tab_removed_id)
    
    def do_update_state(self):
        pass
    
    def add_provider(self, view):
        '''
        Ajouter un fournisseur de complétion à une vue
        @param GtkSourceView view
        '''
        view.get_completion().add_provider(self.provider)
    
    def remove_provider(self, view):
        '''
        Supprimer un fournisseur de complétion à une vue
        @param GtkSourceView view
        '''
        view.get_completion().remove_provider(self.provider)
    
    def on_tab_added(self, window, tab):
        '''
        Ecouteur d'ouverture d'un onglet.
        @param GeditWindow window
        @param GeditTab tab
        '''
        self.add_provider(tab.get_view())
    
    def on_tab_removed(self, window, tab):
        '''
        Ecouteur de fermeture d'un onglet.
        @param GeditWindow window
        @param GeditTab tab
        '''
        self.remove_provider(tab.get_view())
Exemple d'un fournisseur de complétion dans Gedit via un plugin.

Ressources et références

Titre
GtkSourceView 3 Reference Manual
Editeur
Gnome Dev Center
Titre
Evolved Code Completion
Auteurs
  • NAGAOZEN
Éditeur
Github
Date